{"meta":{"version":1,"warehouse":"1.0.2"},"models":{"Asset":[{"_id":"themes/qlandscape/source/js/script.js","path":"js/script.js","modified":0},{"_id":"themes/qlandscape/source/fancybox/jquery.fancybox.pack.js","path":"fancybox/jquery.fancybox.pack.js","modified":0},{"_id":"themes/qlandscape/source/fancybox/jquery.fancybox.js","path":"fancybox/jquery.fancybox.js","modified":0},{"_id":"themes/qlandscape/source/fancybox/jquery.fancybox.css","path":"fancybox/jquery.fancybox.css","modified":0},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-thumbs.js","path":"fancybox/helpers/jquery.fancybox-thumbs.js","modified":0},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-thumbs.css","path":"fancybox/helpers/jquery.fancybox-thumbs.css","modified":0},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-media.js","path":"fancybox/helpers/jquery.fancybox-media.js","modified":0},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-buttons.js","path":"fancybox/helpers/jquery.fancybox-buttons.js","modified":0},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-buttons.css","path":"fancybox/helpers/jquery.fancybox-buttons.css","modified":0},{"_id":"themes/qlandscape/source/fancybox/helpers/fancybox_buttons.png","path":"fancybox/helpers/fancybox_buttons.png","modified":0},{"_id":"themes/qlandscape/source/fancybox/fancybox_sprite@2x.png","path":"fancybox/fancybox_sprite@2x.png","modified":0},{"_id":"themes/qlandscape/source/fancybox/fancybox_sprite.png","path":"fancybox/fancybox_sprite.png","modified":0},{"_id":"themes/qlandscape/source/fancybox/fancybox_overlay.png","path":"fancybox/fancybox_overlay.png","modified":0},{"_id":"themes/qlandscape/source/fancybox/fancybox_loading@2x.gif","path":"fancybox/fancybox_loading@2x.gif","modified":0},{"_id":"themes/qlandscape/source/fancybox/fancybox_loading.gif","path":"fancybox/fancybox_loading.gif","modified":0},{"_id":"themes/qlandscape/source/fancybox/blank.gif","path":"fancybox/blank.gif","modified":0},{"_id":"themes/qlandscape/source/css/style.styl","path":"css/style.styl","modified":0},{"_id":"themes/qlandscape/source/css/images/banner_old.jpg","path":"css/images/banner_old.jpg","modified":0},{"_id":"themes/qlandscape/source/css/images/banner.jpg","path":"css/images/banner.jpg","modified":0},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.woff","path":"css/fonts/fontawesome-webfont.woff","modified":0},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.ttf","path":"css/fonts/fontawesome-webfont.ttf","modified":0},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.svg","path":"css/fonts/fontawesome-webfont.svg","modified":0},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.eot","path":"css/fonts/fontawesome-webfont.eot","modified":0},{"_id":"themes/qlandscape/source/css/fonts/FontAwesome.otf","path":"css/fonts/FontAwesome.otf","modified":0},{"_id":"source/image/wallFunctions/Law_of_the_wall.png","path":"image/wallFunctions/Law_of_the_wall.png","modified":0},{"_id":"source/image/vortex/s.png","path":"image/vortex/s.png","modified":0},{"_id":"source/image/vortex/p.png","path":"image/vortex/p.png","modified":0},{"_id":"source/image/vortex/magVor.png","path":"image/vortex/magVor.png","modified":0},{"_id":"source/image/vortex/Lambda2.png","path":"image/vortex/Lambda2.png","modified":0},{"_id":"source/image/vimext/without_plugin.png","path":"image/vimext/without_plugin.png","modified":0},{"_id":"source/image/vimext/with_plugin.png","path":"image/vimext/with_plugin.png","modified":0},{"_id":"source/image/vimext/k1.gif","path":"image/vimext/k1.gif","modified":0},{"_id":"source/image/vimext/k.gif","path":"image/vimext/k.gif","modified":0},{"_id":"source/image/vimext/fvSolutions.gif","path":"image/vimext/fvSolutions.gif","modified":0},{"_id":"source/image/vimext/fvSchemes.gif","path":"image/vimext/fvSchemes.gif","modified":0},{"_id":"source/image/vimext/RAS.gif","path":"image/vimext/RAS.gif","modified":0},{"_id":"source/image/turbulenceModel/turbulenceModel-3.0.png","path":"image/turbulenceModel/turbulenceModel-3.0.png","modified":0},{"_id":"source/image/turbulenceModel/call.png","path":"image/turbulenceModel/call.png","modified":0},{"_id":"source/image/turbulenceModel/RAS_LES.png","path":"image/turbulenceModel/RAS_LES.png","modified":0},{"_id":"source/image/turbulenceModel/RAS.png","path":"image/turbulenceModel/RAS.png","modified":0},{"_id":"source/image/turbulenceModel/LES.png","path":"image/turbulenceModel/LES.png","modified":0},{"_id":"source/image/paraview_remote/rc3.png","path":"image/paraview_remote/rc3.png","modified":0},{"_id":"source/image/paraview_remote/rc2.png","path":"image/paraview_remote/rc2.png","modified":0},{"_id":"source/image/paraview_remote/rc1.png","path":"image/paraview_remote/rc1.png","modified":0},{"_id":"source/image/paraview_remote/cs3.png","path":"image/paraview_remote/cs3.png","modified":0},{"_id":"source/image/paraview_remote/cs2.png","path":"image/paraview_remote/cs2.png","modified":0},{"_id":"source/image/paraview_remote/cs1.png","path":"image/paraview_remote/cs1.png","modified":0},{"_id":"source/image/juperter_impact.jpg","path":"image/juperter_impact.jpg","modified":0},{"_id":"source/image/fvOptions/fvOptions.png","path":"image/fvOptions/fvOptions.png","modified":0},{"_id":"source/image/cfb.gif","path":"image/cfb.gif","modified":0},{"_id":"source/image/boundaryConditions/boundary-conditions-in-openfoam-90.jpg","path":"image/boundaryConditions/boundary-conditions-in-openfoam-90.jpg","modified":0},{"_id":"source/image/boundaryConditions/JJ.png","path":"image/boundaryConditions/JJ.png","modified":0},{"_id":"source/image/blockMesh/mesh_whole.png","path":"image/blockMesh/mesh_whole.png","modified":0},{"_id":"source/image/blockMesh/mesh_local.png","path":"image/blockMesh/mesh_local.png","modified":0},{"_id":"source/image/TFM/turbulenceModel.png","path":"image/TFM/turbulenceModel.png","modified":0}],"Cache":[{"_id":"source/_posts/Boundary-conditions-in-OpenFOAM1.md","shasum":"4b7e5eef993815574a5cf6f2c90c5f252ea83e6e","modified":1459668444175},{"_id":"source/_posts/Boundary-conditions-in-OpenFOAM2.md","shasum":"3d76f3edce49f5da6b5c5f960737b8af48aa5572","modified":1464624099866},{"_id":"source/_posts/Boundary-conditions-in-OpenFOAM3.md","shasum":"8d73226fbffe3333c8965c41410132ebc91b2c6b","modified":1459669484840},{"_id":"source/_posts/Boundary-conditions-in-OpenFOAM4.md","shasum":"978d66aa715c535c2455ec10614a80508aea831b","modified":1459654934824},{"_id":"source/_posts/CPP-conversion-Of-derived-class-reference-to-base-class-type.md","shasum":"a3048618acb29f7e9922a8d43c8f1b9a4f5ed269","modified":1442150376361},{"_id":"source/_posts/OpenFOAM-install-centOS.md","shasum":"58dfe8b4af623464aeb798388831eafbaa4668b4","modified":1463984958106},{"_id":"source/_posts/OpenFOAM-on-win.md","shasum":"c2653dfaf70b9e6f3aeeda0383fc013ee838885a","modified":1434522343426},{"_id":"source/_posts/OpenFOAM-singlePhase-turbulenceModel.md","shasum":"de15f9f7b3b955ad893923f37ecec8a34edca6e9","modified":1457770289843},{"_id":"source/_posts/OpenFOAM-singlePhase-turbulenceModel2.md","shasum":"e995a61b27c63965d0db18cf75822bf4467a0a5e","modified":1457271335300},{"_id":"source/_posts/OpenFOAMcode1.md","shasum":"763e753cf97c4faabb953e64bf3a624bf558a879","modified":1464771072815},{"_id":"source/_posts/QAndLambda.md","shasum":"570d4dcff7d3b0ede93913d06fc276a29fbe1e07","modified":1463927512155},{"_id":"source/_posts/RTS1.md","shasum":"c79208e585c51322a3081121f3a5fd4f7c3902aa","modified":1461551162469},{"_id":"source/_posts/RTS2.md","shasum":"42f2d00ca69f7072fc431be80adbc0ad15eb7e4c","modified":1457770860646},{"_id":"source/_posts/TFM-TurbulenceModel.md","shasum":"9101147fad3404cc0e7a7818e0ce50406898c260","modified":1443172531359},{"_id":"source/_posts/TurbulenceModel-30-NewModels.md","shasum":"8ce7531e9e420642c6d18578bee2e30d884e5e32","modified":1461550068763},{"_id":"source/_posts/TurbulenceModel-30-RTS.md","shasum":"3836aaa3957236f53d279aebc13f9405b0938ab7","modified":1461550541650},{"_id":"source/_posts/TurbulenceModel-30-macro.md","shasum":"2c583c1b56261525959ec8346097f88af8dbbc99","modified":1461548662363},{"_id":"source/_posts/TurbulenceModel-30-structure.md","shasum":"d7991edc835606615be1163d611fbadd17f857f7","modified":1461508736963},{"_id":"source/_posts/blockMesh-multi-sectional-grading.md","shasum":"e31248e91f763bafca6eda278cce68fa335b82f4","modified":1446557901454},{"_id":"source/_posts/divDevReff.md","shasum":"75b61cb2e0eaace6abc0f84d2ffd49056f0cc9f0","modified":1462255115645},{"_id":"source/_posts/fieldAverage.md","shasum":"6eb6d0dc1c38ec2ac4f9adae26d64becc84a448c","modified":1449837541512},{"_id":"source/_posts/foamTimeAverage.md","shasum":"188e9dafc2131b78849104451b782d4ecf0a26b1","modified":1433381221949},{"_id":"source/_posts/functionObjects.md","shasum":"c4b83f75b1ba67e34fb382d571d9eef1279259d9","modified":1431157325792},{"_id":"source/_posts/fvOptions1.md","shasum":"b24e2ddfc4aa51bc011a33f411d72f088b7e5ea3","modified":1462330269504},{"_id":"source/_posts/fvOptions2.md","shasum":"5e26d6745736eddd649c77d57c2337a55a002f8f","modified":1461675799617},{"_id":"source/_posts/hello-world.md","shasum":"64abc9e312a7399e40fe96ce7ec811f3cdf9f471","modified":1433429335596},{"_id":"source/_posts/kineticTheoryModel.md","shasum":"0d4409d7873b6088129a6fea2f3ef43fe3d77353","modified":1443172478738},{"_id":"source/_posts/kineticTheoryModelSubModels.md","shasum":"dd6f395120df874beab53732648b684e8683aa71","modified":1448545091789},{"_id":"source/_posts/liggghts-howto.md","shasum":"243c33d1e23cd5d037fd4d8cba73588914e99165","modified":1464180570143},{"_id":"source/_posts/paraview-remote.md","shasum":"64e3954fc4720e62f5bef22766506a0ae3f85d8d","modified":1430665100758},{"_id":"source/_posts/separationOfDeclarationAndDefiniton.md","shasum":"ec9d65f13881ed8185e2031e2656cec7abfc26de","modified":1457924386281},{"_id":"source/_posts/timeVaryingBC1.md","shasum":"3c11555fef4216f641e02549b01e9ff5eef9be11","modified":1449909684219},{"_id":"source/_posts/twoPhaseEulerFoam1.md","shasum":"2360b8e6e8fa0245298c82cbcba27b661e33e01d","modified":1431846965037},{"_id":"source/_posts/twoPhaseEulerFoam2.md","shasum":"c9406fadd06aded53b0a3c18f814fdc24b3d5366","modified":1435408933207},{"_id":"source/_posts/twoPhaseEulerFoam23x-twoPhaseSystem.md","shasum":"2a5a3993b0ce53a0340d421779f234c94fb31363","modified":1443171986746},{"_id":"source/_posts/swak4Foam-alpha-water.md","shasum":"53a146835a42d2ce020fbfdc54dd35e6fd02532d","modified":1457924446003},{"_id":"source/_posts/twoPhaseEulerFoam3.md","shasum":"f96c3453da31b05191776ef9dd6a9040445f573c","modified":1464771925539},{"_id":"source/_posts/vimExtensionOpenFOAM.md","shasum":"8d4ffcbc0e951d34a843b995a61d235990bfce37","modified":1439715058124},{"_id":"source/_posts/wallFunctions1.md","shasum":"adff3f8dc69888b8346707de05bb0d3247beaa00","modified":1461683564864},{"_id":"source/_posts/wallFunctions2.md","shasum":"b25265cfd107fc238b67a814a949dbf1962f0a2d","modified":1461552547183},{"_id":"source/_posts/wallFunctions3.md","shasum":"85f2b4785f8cc750125f9cd39663a15b209f56e4","modified":1461553553080},{"_id":"source/_posts/wallFunctions4.md","shasum":"2cc50f0658ffa9b04d022beec74407e43e126b77","modified":1461554075521},{"_id":"source/about/index.md","shasum":"10a962ec78fbedf864ec765d77da39f2c5c49ce0","modified":1461552043575},{"_id":"source/image/blockMesh/mesh_local.png","shasum":"7b6e6740ff53bbe90e8451a0ecb29958931fa2f3","modified":1444134132831},{"_id":"source/image/blockMesh/mesh_whole.png","shasum":"0bd51e0bd404766d3ee6ca912baedc3347d1f7e3","modified":1444134113697},{"_id":"source/image/boundaryConditions/JJ.png","shasum":"c042ad838f12a5a99a3a44fc6381891687dd01b7","modified":1459592423179},{"_id":"source/image/paraview_remote/cs1.png","shasum":"69294cb955e50f86bc28643a5c41ae2783595554","modified":1430657188248},{"_id":"source/image/paraview_remote/cs2.png","shasum":"a4a46c2535f33c3ec738954cd8cd84bd26f54bef","modified":1430657209025},{"_id":"source/image/paraview_remote/cs3.png","shasum":"7de7c864734d5a54a22733a33118d25c94b7e9c1","modified":1430657226659},{"_id":"source/image/paraview_remote/rc1.png","shasum":"228d4846c2a933b9179655523d875ff614510464","modified":1430663744812},{"_id":"source/image/paraview_remote/rc2.png","shasum":"21caf7bd9db1255208092c9679454ab54e88ed5d","modified":1430663753156},{"_id":"source/image/paraview_remote/rc3.png","shasum":"a4abcbb2ab023a886ec839ff2d92442fa4f1c242","modified":1430663767868},{"_id":"source/image/turbulenceModel/RAS.png","shasum":"eccc908b980c26e150079ff8a203117868106039","modified":1448453095770},{"_id":"source/image/turbulenceModel/RAS_LES.png","shasum":"f3b2424dec34fa90a6507c82872a69ccabd1abc2","modified":1448453221404},{"_id":"source/image/vimext/RAS.gif","shasum":"2407a0a0b7c3ff205aac27087a3f3d11c8f2e4e7","modified":1439713267406},{"_id":"source/image/vimext/with_plugin.png","shasum":"5b2ea34a9a0cca6dee4423c4e9f459741cda2b6e","modified":1439709677009},{"_id":"source/image/vortex/magVor.png","shasum":"4a361ca37fa0f4d5c824ce117764cae82ec1d4d2","modified":1463923593038},{"_id":"source/image/boundaryConditions/boundary-conditions-in-openfoam-90.jpg","shasum":"1a2cbfb0cd1611cf3b7b43ad4e9efc9331d34758","modified":1459592075450},{"_id":"source/image/vimext/fvSolutions.gif","shasum":"42ce1778551925b84196de7585ab6c47ff32508d","modified":1439713628007},{"_id":"source/image/vimext/k.gif","shasum":"9dda4487f383ba0d9db80e39c4786f8c6d51cf16","modified":1439711345478},{"_id":"source/image/vimext/k1.gif","shasum":"586bf8e37a5aa05df42ba38ae6ce6502c185ee47","modified":1439712230113},{"_id":"source/image/vimext/without_plugin.png","shasum":"db428ec8fcbe0fc5ba435d08a425118b07be3c72","modified":1439710716999},{"_id":"source/image/vortex/p.png","shasum":"eaec9bd695f6b6898c82b70536c4dd543e4d431d","modified":1463922865495},{"_id":"source/image/vortex/s.png","shasum":"8aa6d6c4f6ce68a5c7ecaae441e9d73ffac88827","modified":1463923279139},{"_id":"source/image/wallFunctions/Law_of_the_wall.png","shasum":"222087402abaf5766813b498c6bf687e927c343d","modified":1460982620631},{"_id":"source/image/juperter_impact.jpg","shasum":"dee77d2e2febddad934648674af020551f3799e3","modified":1430479888349},{"_id":"source/image/turbulenceModel/LES.png","shasum":"8ee30d659a371274bb39339d9f440155eea95cdb","modified":1448441805016},{"_id":"source/image/turbulenceModel/call.png","shasum":"34e39e223bf78b5302dfcdfea1afdd5790e8e797","modified":1448448706092},{"_id":"source/image/fvOptions/fvOptions.png","shasum":"c1c7edffe58eb7b826847ba2a504bd27f425f102","modified":1458466530947},{"_id":"source/image/vimext/fvSchemes.gif","shasum":"3423ab7bfc48ea789c390813a4e0a09a0ad34067","modified":1439714027757},{"_id":"themes/qlandscape/Gruntfile.js","shasum":"71adaeaac1f3cc56e36c49d549b8d8a72235c9b9","modified":1428329026286},{"_id":"themes/qlandscape/LICENSE","shasum":"c480fce396b23997ee23cc535518ffaaf7f458f8","modified":1428329026286},{"_id":"themes/qlandscape/README.md","shasum":"c7e83cfe8f2c724fc9cac32bd71bb5faf9ceeddb","modified":1428329026286},{"_id":"themes/qlandscape/_config.yml","shasum":"7050d7f590cf47db324ae528e7b6ccf9655f2309","modified":1449457908939},{"_id":"themes/qlandscape/layout/_partial/after-footer.ejs","shasum":"6bb1249d064fccbf1131f6b5615d94dfba147ce0","modified":1430483181006},{"_id":"themes/qlandscape/layout/_partial/archive-post.ejs","shasum":"c7a71425a946d05414c069ec91811b5c09a92c47","modified":1428329026328},{"_id":"themes/qlandscape/layout/_partial/archive.ejs","shasum":"d7de6421497ffaf65e4f5fe4bed71fcea51fde80","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/article.ejs","shasum":"9dc437e6ca91d277c05848706c675dc9938c1314","modified":1431695842825},{"_id":"themes/qlandscape/layout/_partial/comment.ejs","shasum":"d0c5e5ebea98e3e4a0bb44de30ab168108485fe5","modified":1430486076030},{"_id":"themes/qlandscape/layout/_partial/footer.ejs","shasum":"1f15bfa00f764f1bf74813fba1ba5047690ef8be","modified":1430652932337},{"_id":"themes/qlandscape/layout/_partial/head.ejs","shasum":"da9a4ab32efc44c098f317fe64e2335989929b0e","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/google-analytics.ejs","shasum":"03b50f7e5a066b0a97814b9050e32e3f16180e69","modified":1433073541263},{"_id":"themes/qlandscape/layout/_partial/header.ejs","shasum":"08f8d223e07fb3146b68edcbf5e437acbada6fa3","modified":1431695861370},{"_id":"themes/qlandscape/layout/_partial/math-jax.ejs","shasum":"cee3f094a1f454765be308d8ad86524cdc5b71a0","modified":1428828174911},{"_id":"themes/qlandscape/layout/_partial/mobile-nav.ejs","shasum":"e952a532dfc583930a666b9d4479c32d4a84b44e","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/post/category.ejs","shasum":"c6bcd0e04271ffca81da25bcff5adf3d46f02fc0","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/post/date.ejs","shasum":"6197802873157656e3077c5099a7dda3d3b01c29","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/post/gallery.ejs","shasum":"3d9d81a3c693ff2378ef06ddb6810254e509de5b","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/post/nav.ejs","shasum":"f26d30355ba9144c51e700e8edc6a4ab6144ff9a","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/post/tag.ejs","shasum":"2fcb0bf9c8847a644167a27824c9bb19ac74dd14","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/post/title.ejs","shasum":"2f275739b6f1193c123646a5a31f37d48644c667","modified":1428329026329},{"_id":"themes/qlandscape/layout/_partial/sidebar.ejs","shasum":"930da35cc2d447a92e5ee8f835735e6fd2232469","modified":1428329026329},{"_id":"themes/qlandscape/layout/_widget/archive.ejs","shasum":"985fbeb01142b9d526cda8ebc372c1d361d69a6b","modified":1428329026329},{"_id":"themes/qlandscape/layout/_widget/category.ejs","shasum":"36ab37878129d152e3cbdeb839c08e52af1acd58","modified":1428329026329},{"_id":"themes/qlandscape/layout/_widget/recent_posts.ejs","shasum":"feba7c00fa59ba13bf870b358a499fde4473d335","modified":1428329026329},{"_id":"themes/qlandscape/layout/_widget/tag.ejs","shasum":"b3f321ddda6be2702a286d5b11af9533509506fb","modified":1428329026329},{"_id":"themes/qlandscape/layout/_widget/tagcloud.ejs","shasum":"34dc8cdd96cdb41dd11cb7513f13714373e5104a","modified":1428329026329},{"_id":"themes/qlandscape/layout/archive.ejs","shasum":"2703b07cc8ac64ae46d1d263f4653013c7e1666b","modified":1428329026311},{"_id":"themes/qlandscape/layout/category.ejs","shasum":"765426a9c8236828dc34759e604cc2c52292835a","modified":1428329026311},{"_id":"themes/qlandscape/layout/index.ejs","shasum":"aa1b4456907bdb43e629be3931547e2d29ac58c8","modified":1428329026311},{"_id":"themes/qlandscape/layout/layout.ejs","shasum":"5cf980b9ab41ad305b1ca08a345d2c605d318379","modified":1428828426504},{"_id":"themes/qlandscape/layout/page.ejs","shasum":"7d80e4e36b14d30a7cd2ac1f61376d9ebf264e8b","modified":1428329026311},{"_id":"themes/qlandscape/layout/post.ejs","shasum":"7d80e4e36b14d30a7cd2ac1f61376d9ebf264e8b","modified":1428329026311},{"_id":"themes/qlandscape/layout/tag.ejs","shasum":"eaa7b4ccb2ca7befb90142e4e68995fb1ea68b2e","modified":1428329026311},{"_id":"themes/qlandscape/package.json","shasum":"46ad3f425251f251186595c59e17c291275d7ccc","modified":1428758731708},{"_id":"themes/qlandscape/scripts/fancybox.js","shasum":"aa411cd072399df1ddc8e2181a3204678a5177d9","modified":1428329026326},{"_id":"themes/qlandscape/source/css/_extend.styl","shasum":"222fbe6d222531d61c1ef0f868c90f747b1c2ced","modified":1428329026332},{"_id":"themes/qlandscape/source/css/_partial/archive.styl","shasum":"db15f5677dc68f1730e82190bab69c24611ca292","modified":1428329026332},{"_id":"themes/qlandscape/source/css/_partial/article.styl","shasum":"10685f8787a79f79c9a26c2f943253450c498e3e","modified":1428329026332},{"_id":"themes/qlandscape/source/css/_partial/comment.styl","shasum":"79d280d8d203abb3bd933ca9b8e38c78ec684987","modified":1428329026333},{"_id":"themes/qlandscape/source/css/_partial/footer.styl","shasum":"e35a060b8512031048919709a8e7b1ec0e40bc1b","modified":1428329026332},{"_id":"themes/qlandscape/source/css/_partial/header.styl","shasum":"85ab11e082f4dd86dde72bed653d57ec5381f30c","modified":1428329026333},{"_id":"themes/qlandscape/source/css/_partial/highlight.styl","shasum":"fedaeebafa8885eaf2f73fde526dac9127332e5c","modified":1428837274289},{"_id":"themes/qlandscape/source/css/_partial/mobile.styl","shasum":"a399cf9e1e1cec3e4269066e2948d7ae5854d745","modified":1428329026333},{"_id":"themes/qlandscape/source/css/_partial/sidebar-aside.styl","shasum":"890349df5145abf46ce7712010c89237900b3713","modified":1428329026333},{"_id":"themes/qlandscape/source/css/_partial/sidebar-bottom.styl","shasum":"bc5487b9a0bfe5f745423331824d3f3637ccd430","modified":1428329026333},{"_id":"themes/qlandscape/source/css/_partial/sidebar.styl","shasum":"b7bdc11effa98c6d88850eff75634e2ea9207c14","modified":1428329026334},{"_id":"themes/qlandscape/source/css/_util/grid.styl","shasum":"0bf55ee5d09f193e249083602ac5fcdb1e571aed","modified":1428329026334},{"_id":"themes/qlandscape/source/css/_util/mixin.styl","shasum":"44f32767d9fd3c1c08a60d91f181ee53c8f0dbb3","modified":1428329026334},{"_id":"themes/qlandscape/source/css/_variables.styl","shasum":"9592c4e6902a12d07409d0c8c770029df64b7ccb","modified":1428836490958},{"_id":"themes/qlandscape/source/css/fonts/FontAwesome.otf","shasum":"b5b4f9be85f91f10799e87a083da1d050f842734","modified":1428329026335},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.eot","shasum":"7619748fe34c64fb157a57f6d4ef3678f63a8f5e","modified":1428329026335},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.woff","shasum":"04c3bf56d87a0828935bd6b4aee859995f321693","modified":1428329026336},{"_id":"themes/qlandscape/source/css/style.styl","shasum":"278d1458b968a151c27b87643191d2d7a8129511","modified":1428329026335},{"_id":"themes/qlandscape/source/fancybox/blank.gif","shasum":"2daeaa8b5f19f0bc209d976c02bd6acb51b00b0a","modified":1428329026337},{"_id":"themes/qlandscape/source/fancybox/fancybox_loading.gif","shasum":"1a755fb2599f3a313cc6cfdb14df043f8c14a99c","modified":1428329026336},{"_id":"themes/qlandscape/source/fancybox/fancybox_loading@2x.gif","shasum":"273b123496a42ba45c3416adb027cd99745058b0","modified":1428329026336},{"_id":"themes/qlandscape/source/fancybox/fancybox_overlay.png","shasum":"b3a4ee645ba494f52840ef8412015ba0f465dbe0","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/fancybox_sprite.png","shasum":"17df19f97628e77be09c352bf27425faea248251","modified":1428329026337},{"_id":"themes/qlandscape/source/fancybox/fancybox_sprite@2x.png","shasum":"30c58913f327e28f466a00f4c1ac8001b560aed8","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/helpers/fancybox_buttons.png","shasum":"e385b139516c6813dcd64b8fc431c364ceafe5f3","modified":1428329026336},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-buttons.js","shasum":"dc3645529a4bf72983a39fa34c1eb9146e082019","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-buttons.css","shasum":"1a9d8e5c22b371fcc69d4dbbb823d9c39f04c0c8","modified":1428329026336},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-media.js","shasum":"294420f9ff20f4e3584d212b0c262a00a96ecdb3","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-thumbs.css","shasum":"4ac329c16a5277592fc12a37cca3d72ca4ec292f","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/helpers/jquery.fancybox-thumbs.js","shasum":"47da1ae5401c24b5c17cc18e2730780f5c1a7a0c","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/jquery.fancybox.css","shasum":"aaa582fb9eb4b7092dc69fcb2d5b1c20cca58ab6","modified":1428329026338},{"_id":"themes/qlandscape/source/fancybox/jquery.fancybox.js","shasum":"d08b03a42d5c4ba456ef8ba33116fdbb7a9cabed","modified":1428329026339},{"_id":"themes/qlandscape/source/fancybox/jquery.fancybox.pack.js","shasum":"9e0d51ca1dbe66f6c0c7aefd552dc8122e694a6e","modified":1428329026339},{"_id":"themes/qlandscape/source/js/script.js","shasum":"2876e0b19ce557fca38d7c6f49ca55922ab666a1","modified":1428329026328},{"_id":"source/image/TFM/turbulenceModel.png","shasum":"60da2c87e3e3f8bfef2b8f0710f007c5fced7be2","modified":1442638340335},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.ttf","shasum":"7f09c97f333917034ad08fa7295e916c9f72fd3f","modified":1428329026339},{"_id":"themes/qlandscape/source/css/fonts/fontawesome-webfont.svg","shasum":"46fcc0194d75a0ddac0a038aee41b23456784814","modified":1428329026340},{"_id":"themes/qlandscape/source/css/images/banner.jpg","shasum":"a01c6bf929f307bc86b81e025516216904a69791","modified":1428763266214},{"_id":"themes/qlandscape/source/css/images/banner_old.jpg","shasum":"843d9d47bf2b7b75495db11b3d765efaaae442a9","modified":1428329026340},{"_id":"source/image/turbulenceModel/turbulenceModel-3.0.png","shasum":"56c30c4a5e842c7b5988237ffec05f93fd25f29d","modified":1461510064767},{"_id":"source/image/vortex/Lambda2.png","shasum":"a12e0a45fccfcc04250f42bbc926a5df0316a48b","modified":1463921596401},{"_id":"source/image/cfb.gif","shasum":"8a83a3d53f1fef4e18ce70df40f76c4a67ba2691","modified":1426758327667},{"_id":"public/js/script.js","modified":1463930478652,"shasum":"2876e0b19ce557fca38d7c6f49ca55922ab666a1"},{"_id":"public/fancybox/jquery.fancybox.pack.js","modified":1463930478658,"shasum":"9e0d51ca1dbe66f6c0c7aefd552dc8122e694a6e"},{"_id":"public/fancybox/jquery.fancybox.js","modified":1463930478668,"shasum":"d08b03a42d5c4ba456ef8ba33116fdbb7a9cabed"},{"_id":"public/fancybox/jquery.fancybox.css","modified":1463930478672,"shasum":"aaa582fb9eb4b7092dc69fcb2d5b1c20cca58ab6"},{"_id":"public/fancybox/helpers/jquery.fancybox-thumbs.js","modified":1463930478674,"shasum":"47da1ae5401c24b5c17cc18e2730780f5c1a7a0c"},{"_id":"public/fancybox/helpers/jquery.fancybox-thumbs.css","modified":1463930478682,"shasum":"4ac329c16a5277592fc12a37cca3d72ca4ec292f"},{"_id":"public/fancybox/helpers/jquery.fancybox-media.js","modified":1463930478684,"shasum":"294420f9ff20f4e3584d212b0c262a00a96ecdb3"},{"_id":"public/fancybox/helpers/jquery.fancybox-buttons.js","modified":1463930478686,"shasum":"dc3645529a4bf72983a39fa34c1eb9146e082019"},{"_id":"public/fancybox/helpers/jquery.fancybox-buttons.css","modified":1463930478688,"shasum":"1a9d8e5c22b371fcc69d4dbbb823d9c39f04c0c8"},{"_id":"public/fancybox/helpers/fancybox_buttons.png","modified":1463930478694,"shasum":"e385b139516c6813dcd64b8fc431c364ceafe5f3"},{"_id":"public/fancybox/fancybox_sprite@2x.png","modified":1463930478699,"shasum":"30c58913f327e28f466a00f4c1ac8001b560aed8"},{"_id":"public/fancybox/fancybox_sprite.png","modified":1463930478702,"shasum":"17df19f97628e77be09c352bf27425faea248251"},{"_id":"public/fancybox/fancybox_overlay.png","modified":1463930478704,"shasum":"b3a4ee645ba494f52840ef8412015ba0f465dbe0"},{"_id":"public/fancybox/fancybox_loading@2x.gif","modified":1463930478706,"shasum":"273b123496a42ba45c3416adb027cd99745058b0"},{"_id":"public/fancybox/fancybox_loading.gif","modified":1463930478708,"shasum":"1a755fb2599f3a313cc6cfdb14df043f8c14a99c"},{"_id":"public/fancybox/blank.gif","modified":1463930478710,"shasum":"2daeaa8b5f19f0bc209d976c02bd6acb51b00b0a"},{"_id":"public/css/style.css","modified":1463930479876,"shasum":"60ecf6427b6bbd1e66b8f0dbbd5d2ded4b6bff94"},{"_id":"public/css/images/banner_old.jpg","modified":1463930480198,"shasum":"843d9d47bf2b7b75495db11b3d765efaaae442a9"},{"_id":"public/css/images/banner.jpg","modified":1463930480202,"shasum":"a01c6bf929f307bc86b81e025516216904a69791"},{"_id":"public/css/fonts/fontawesome-webfont.woff","modified":1463930480208,"shasum":"04c3bf56d87a0828935bd6b4aee859995f321693"},{"_id":"public/css/fonts/fontawesome-webfont.ttf","modified":1463930480214,"shasum":"7f09c97f333917034ad08fa7295e916c9f72fd3f"},{"_id":"public/css/fonts/fontawesome-webfont.svg","modified":1463930480218,"shasum":"46fcc0194d75a0ddac0a038aee41b23456784814"},{"_id":"public/css/fonts/fontawesome-webfont.eot","modified":1463930480221,"shasum":"7619748fe34c64fb157a57f6d4ef3678f63a8f5e"},{"_id":"public/css/fonts/FontAwesome.otf","modified":1463930480226,"shasum":"b5b4f9be85f91f10799e87a083da1d050f842734"},{"_id":"public/image/wallFunctions/Law_of_the_wall.png","modified":1463930480231,"shasum":"222087402abaf5766813b498c6bf687e927c343d"},{"_id":"public/image/vortex/s.png","modified":1463930480236,"shasum":"8aa6d6c4f6ce68a5c7ecaae441e9d73ffac88827"},{"_id":"public/image/vortex/p.png","modified":1463930480240,"shasum":"eaec9bd695f6b6898c82b70536c4dd543e4d431d"},{"_id":"public/image/vortex/magVor.png","modified":1463930480244,"shasum":"4a361ca37fa0f4d5c824ce117764cae82ec1d4d2"},{"_id":"public/image/vortex/Lambda2.png","modified":1463930480263,"shasum":"a12e0a45fccfcc04250f42bbc926a5df0316a48b"},{"_id":"public/image/vimext/without_plugin.png","modified":1463930480273,"shasum":"db428ec8fcbe0fc5ba435d08a425118b07be3c72"},{"_id":"public/image/vimext/with_plugin.png","modified":1463930480276,"shasum":"5b2ea34a9a0cca6dee4423c4e9f459741cda2b6e"},{"_id":"public/image/vimext/k1.gif","modified":1463930480279,"shasum":"586bf8e37a5aa05df42ba38ae6ce6502c185ee47"},{"_id":"public/image/vimext/k.gif","modified":1463930480292,"shasum":"9dda4487f383ba0d9db80e39c4786f8c6d51cf16"},{"_id":"public/image/vimext/fvSolutions.gif","modified":1463930480297,"shasum":"42ce1778551925b84196de7585ab6c47ff32508d"},{"_id":"public/image/vimext/fvSchemes.gif","modified":1463930480301,"shasum":"3423ab7bfc48ea789c390813a4e0a09a0ad34067"},{"_id":"public/image/vimext/RAS.gif","modified":1463930480303,"shasum":"2407a0a0b7c3ff205aac27087a3f3d11c8f2e4e7"},{"_id":"public/image/turbulenceModel/turbulenceModel-3.0.png","modified":1463930480311,"shasum":"56c30c4a5e842c7b5988237ffec05f93fd25f29d"},{"_id":"public/image/turbulenceModel/call.png","modified":1463930480316,"shasum":"34e39e223bf78b5302dfcdfea1afdd5790e8e797"},{"_id":"public/image/turbulenceModel/RAS_LES.png","modified":1463930480320,"shasum":"f3b2424dec34fa90a6507c82872a69ccabd1abc2"},{"_id":"public/image/turbulenceModel/RAS.png","modified":1463930480322,"shasum":"eccc908b980c26e150079ff8a203117868106039"},{"_id":"public/image/turbulenceModel/LES.png","modified":1463930480325,"shasum":"8ee30d659a371274bb39339d9f440155eea95cdb"},{"_id":"public/image/paraview_remote/rc3.png","modified":1463930480327,"shasum":"a4abcbb2ab023a886ec839ff2d92442fa4f1c242"},{"_id":"public/image/paraview_remote/rc2.png","modified":1463930480330,"shasum":"21caf7bd9db1255208092c9679454ab54e88ed5d"},{"_id":"public/image/paraview_remote/rc1.png","modified":1463930480334,"shasum":"228d4846c2a933b9179655523d875ff614510464"},{"_id":"public/image/paraview_remote/cs3.png","modified":1463930480336,"shasum":"7de7c864734d5a54a22733a33118d25c94b7e9c1"},{"_id":"public/image/paraview_remote/cs2.png","modified":1463930480338,"shasum":"a4a46c2535f33c3ec738954cd8cd84bd26f54bef"},{"_id":"public/image/paraview_remote/cs1.png","modified":1463930480340,"shasum":"69294cb955e50f86bc28643a5c41ae2783595554"},{"_id":"public/image/juperter_impact.jpg","modified":1463930480344,"shasum":"dee77d2e2febddad934648674af020551f3799e3"},{"_id":"public/image/fvOptions/fvOptions.png","modified":1463930480350,"shasum":"c1c7edffe58eb7b826847ba2a504bd27f425f102"},{"_id":"public/image/cfb.gif","modified":1463930480372,"shasum":"8a83a3d53f1fef4e18ce70df40f76c4a67ba2691"},{"_id":"public/image/boundaryConditions/boundary-conditions-in-openfoam-90.jpg","modified":1463930480388,"shasum":"1a2cbfb0cd1611cf3b7b43ad4e9efc9331d34758"},{"_id":"public/image/boundaryConditions/JJ.png","modified":1463930480391,"shasum":"c042ad838f12a5a99a3a44fc6381891687dd01b7"},{"_id":"public/image/blockMesh/mesh_whole.png","modified":1463930480393,"shasum":"0bd51e0bd404766d3ee6ca912baedc3347d1f7e3"},{"_id":"public/image/blockMesh/mesh_local.png","modified":1463930480395,"shasum":"7b6e6740ff53bbe90e8451a0ecb29958931fa2f3"},{"_id":"public/image/TFM/turbulenceModel.png","modified":1463930480399,"shasum":"60da2c87e3e3f8bfef2b8f0710f007c5fced7be2"},{"_id":"public/about/index.html","modified":1463930480496,"shasum":"6df864941c2ffbbb8266212e73d7fc872da849e1"},{"_id":"public/2016/05/22/QAndLambda/index.html","modified":1463930480535,"shasum":"de63a6b3b6fc1d4a6afbd40a0fa2e0514b49b3b6"},{"_id":"public/2016/05/03/divDevReff/index.html","modified":1463930480553,"shasum":"75ed0e55db060075965370ed3078b190282378b2"},{"_id":"public/2016/05/03/liggghts-howto/index.html","modified":1464180602506,"shasum":"24321a8ba8c83eae2e36588948b51de673822e2c"},{"_id":"public/2016/04/25/wallFunctions4/index.html","modified":1463930480615,"shasum":"5cfed8ab7107c632efe92343f10717e596982cb8"},{"_id":"public/2016/04/25/wallFunctions3/index.html","modified":1463930480652,"shasum":"4b3e0d123e9ef1398bb37a052f3d610235a9a3b8"},{"_id":"public/2016/04/25/wallFunctions2/index.html","modified":1463930480671,"shasum":"8fd53205fd014a36d405de29e41b11b1e91837ac"},{"_id":"public/2016/04/25/wallFunctions1/index.html","modified":1463930480688,"shasum":"27f007914cbfe8aa0d4f3af9cb377ed9df545fb3"},{"_id":"public/2016/04/25/TurbulenceModel-30-macro/index.html","modified":1463930480714,"shasum":"786b007f73f1f901544145bdc3e3615a1c52a92b"},{"_id":"public/2016/04/24/TurbulenceModel-30-NewModels/index.html","modified":1463930480740,"shasum":"098f5834308f5adaba7ffba6f5bce42cf19c8446"},{"_id":"public/2016/04/24/TurbulenceModel-30-RTS/index.html","modified":1463930480767,"shasum":"89f6281a0677aaf8cc115308a9940c65a26e1d8c"},{"_id":"public/2016/04/24/TurbulenceModel-30-structure/index.html","modified":1463930480787,"shasum":"779473f5d16870f8d2650877b28f6a512db1cc65"},{"_id":"public/2016/04/02/Boundary-conditions-in-OpenFOAM4/index.html","modified":1463930480812,"shasum":"8ca8cf699b19ec2947df5459e32737be21b625fb"},{"_id":"public/2016/04/02/Boundary-conditions-in-OpenFOAM3/index.html","modified":1463930480836,"shasum":"92f29f866f755f16b8b29889e1a0b29b1f581da3"},{"_id":"public/2016/04/02/Boundary-conditions-in-OpenFOAM2/index.html","modified":1464624137400,"shasum":"11cf0f86d44a02653d078555c58d3f5fddcde43e"},{"_id":"public/2016/04/02/Boundary-conditions-in-OpenFOAM1/index.html","modified":1463930480897,"shasum":"f8b6adf86f73476b3864b3a9f06022c24b1322f3"},{"_id":"public/2016/03/20/fvOptions2/index.html","modified":1463930480926,"shasum":"58e4520543a7a573e717c88d36cbdbde9ee400c4"},{"_id":"public/2016/03/20/fvOptions1/index.html","modified":1463930480950,"shasum":"d3c2e0e802ef79b7f723f6a91c83301e472a74bd"},{"_id":"public/2016/03/12/RTS2/index.html","modified":1463930480967,"shasum":"63e6918d6ce1f27ea387ddd433592471712340af"},{"_id":"public/2016/03/12/RTS1/index.html","modified":1463930480992,"shasum":"2167ebf0d27ad39f06ecbf6867e9607659c26b56"},{"_id":"public/2016/03/06/separationOfDeclarationAndDefiniton/index.html","modified":1463930481016,"shasum":"f795d560827572593f828e1121f3ad20c8f8dcd8"},{"_id":"public/2016/03/06/OpenFOAM-singlePhase-turbulenceModel2/index.html","modified":1463930481053,"shasum":"6bdae90c9eb3289f87e4527cbcb6148a3168989c"},{"_id":"public/2015/12/12/timeVaryingBC1/index.html","modified":1463930481074,"shasum":"e35c6d63b2adddeaf82cb005cfbae5a6969cd1a1"},{"_id":"public/2015/11/25/swak4Foam-alpha-water/index.html","modified":1463930481086,"shasum":"1a2a690812b05de2ef4a3bb111d139548f589bf3"},{"_id":"public/2015/11/25/OpenFOAM-singlePhase-turbulenceModel/index.html","modified":1463930481131,"shasum":"9843f959ccb5e4bee765968b2673f4fc43050308"},{"_id":"public/2015/10/06/blockMesh-multi-sectional-grading/index.html","modified":1463930481147,"shasum":"bd85ce38d903fb427873a77fc597c109e8081bbd"},{"_id":"public/2015/09/19/TFM-TurbulenceModel/index.html","modified":1463930481188,"shasum":"a7deee203822e6e1a534c99926af623f6e9371a0"},{"_id":"public/2015/09/19/kineticTheoryModelSubModels/index.html","modified":1463930481250,"shasum":"888a798abf5aa009f6f0eaa057da9265b029d0af"},{"_id":"public/2015/09/19/kineticTheoryModel/index.html","modified":1463930481284,"shasum":"3110f8192a29b3fd33f9f125625339782e7d993e"},{"_id":"public/2015/09/13/CPP-conversion-Of-derived-class-reference-to-base-class-type/index.html","modified":1463930481301,"shasum":"c27e4ab7ec6be4c9a616d15ac3638594701eade6"},{"_id":"public/2015/09/13/OpenFOAM-install-centOS/index.html","modified":1463984984728,"shasum":"a5e748c78e0a94157fe6afc5a4eb275c7a557bea"},{"_id":"public/2015/09/07/twoPhaseEulerFoam23x-twoPhaseSystem/index.html","modified":1463930481345,"shasum":"e8c724c5e6d8b4a619a96c87b37523c4d122daef"},{"_id":"public/2015/08/16/vimExtensionOpenFOAM/index.html","modified":1463930481360,"shasum":"26646e5d300b0216623c24829fcf423885d88275"},{"_id":"public/2015/06/27/twoPhaseEulerFoam3/index.html","modified":1464771949275,"shasum":"35d6be850fd073e02853f077112eeea6a017506e"},{"_id":"public/2015/06/14/OpenFOAM-on-win/index.html","modified":1463930481414,"shasum":"e125bd1c5c2fecb39ff0145cbe4dadaece325bd7"},{"_id":"public/2015/05/31/foamTimeAverage/index.html","modified":1463930481436,"shasum":"17dc1154cf758d0596978d472c888a17c590504a"},{"_id":"public/2015/05/17/twoPhaseEulerFoam2/index.html","modified":1463930481455,"shasum":"60965f687b91c9728838a1db7beff90827fde824"},{"_id":"public/2015/05/17/twoPhaseEulerFoam1/index.html","modified":1463930481468,"shasum":"88e54d8d66a58ed189732a73f11235051378e855"},{"_id":"public/2015/05/17/OpenFOAMcode1/index.html","modified":1464771123063,"shasum":"d1a3e28b937ce73b0208bf530e2b81fed7f042fc"},{"_id":"public/2015/05/09/functionObjects/index.html","modified":1463930481514,"shasum":"5d8012a2382fbb406a223850e6776f9434669703"},{"_id":"public/2015/05/03/paraview-remote/index.html","modified":1463930481527,"shasum":"fe29d06d8d4dde25019b4b72be4d1ae6af7b0475"},{"_id":"public/2015/04/12/fieldAverage/index.html","modified":1463930481562,"shasum":"b97c94a461965a5f45e7f6e8106ebcd305c8420f"},{"_id":"public/2015/04/04/hello-world/index.html","modified":1463930481576,"shasum":"3b01c51817c8a689eb551160d67c7f41178e227c"},{"_id":"public/archives/index.html","modified":1463930481604,"shasum":"a6dac8951912eeac32778ba3ff4ae17b4fb1e0c1"},{"_id":"public/archives/page/2/index.html","modified":1463930481626,"shasum":"0cf9cf1dbc72a4903f93760bfaa4df3491995963"},{"_id":"public/archives/page/3/index.html","modified":1463930481649,"shasum":"367e0def1e809f1e02c9d0e1e665e0fd7723eba1"},{"_id":"public/archives/page/4/index.html","modified":1463930481683,"shasum":"b41947e7a1534c5de982ff6b2d99597c8e351ce4"},{"_id":"public/archives/page/5/index.html","modified":1463930481693,"shasum":"a158ccc67eeb4dd44f024d2e6075b15178298a70"},{"_id":"public/archives/2015/index.html","modified":1463930481717,"shasum":"175ad2f88dea7bcd82be9ef856e6d5718e496a5c"},{"_id":"public/archives/2015/page/2/index.html","modified":1463930481740,"shasum":"d9e4351aadd68931c8971ae02cccd9b3a11ce4c0"},{"_id":"public/archives/2015/page/3/index.html","modified":1463930481748,"shasum":"5eb0452c71e0890cdbfb05d0df5889575f189272"},{"_id":"public/archives/2015/04/index.html","modified":1463930481758,"shasum":"a01dded021d40347a6b8f66be4946fc1ea60ca91"},{"_id":"public/archives/2015/05/index.html","modified":1463930481777,"shasum":"1073b8ce81b6adf21d7b1af3dd470b40fac7268f"},{"_id":"public/archives/2015/06/index.html","modified":1463930481787,"shasum":"530bf1732fab36b0cebf71c4171958403deaf0cf"},{"_id":"public/archives/2015/08/index.html","modified":1463930481794,"shasum":"b5997749daacfe4046d4ecde10ad3bb9cff3ae29"},{"_id":"public/archives/2015/09/index.html","modified":1463930481814,"shasum":"f09e0bb9408901623abf20a51e1a2f92ee9e4b7a"},{"_id":"public/archives/2015/10/index.html","modified":1463930481821,"shasum":"758f994b02f7e7a9fbb9888a4d41f8b7c8358551"},{"_id":"public/archives/2015/11/index.html","modified":1463930481830,"shasum":"b81c4188971465e8892d9cf5a1c83c0c97eac142"},{"_id":"public/archives/2015/12/index.html","modified":1463930481837,"shasum":"db66e86847245ddbb273117f687a3faa693246fa"},{"_id":"public/archives/2016/index.html","modified":1463930481867,"shasum":"e91d48ed321378ad299be62d470da7400f535a4a"},{"_id":"public/archives/2016/page/2/index.html","modified":1463930481889,"shasum":"dfb918506c821983a39440f5f13e3d4604a60f56"},{"_id":"public/archives/2016/page/3/index.html","modified":1463930481897,"shasum":"9c53cb005792d60bc06810b8e337bd0b3e7ceaf7"},{"_id":"public/archives/2016/03/index.html","modified":1463930481917,"shasum":"2850c673b4b634e6ef40afb72a791034c124cc6d"},{"_id":"public/archives/2016/04/index.html","modified":1463930481940,"shasum":"0fd0b8bd7539affc35ed7e3633c3bc32534f696f"},{"_id":"public/archives/2016/04/page/2/index.html","modified":1463930481954,"shasum":"d9e27f250ca171a4f37e221e5a964b0ed90d197b"},{"_id":"public/archives/2016/05/index.html","modified":1463930481964,"shasum":"e9f132e42ecd594f419342f96cdea9f76497ad9b"},{"_id":"public/atom.xml","modified":1464624138681,"shasum":"2e919b3c074b756efbc204ce03881574ed08c673"},{"_id":"public/categories/OpenFOAM/index.html","modified":1463930481999,"shasum":"cba994b79aa65452216587ada73c74eb4d26d74e"},{"_id":"public/categories/OpenFOAM/page/2/index.html","modified":1463930482026,"shasum":"0884606037a28b6e732047490c82504c71435af9"},{"_id":"public/categories/OpenFOAM/page/3/index.html","modified":1463930482050,"shasum":"20aee8cf5d8a2f9dff5b0ecef72f985cc579898b"},{"_id":"public/categories/OpenFOAM/page/4/index.html","modified":1463930482064,"shasum":"c6f8e4ad69885e7ecbc784329ece05b77f3536d0"},{"_id":"public/categories/vim/index.html","modified":1463930482072,"shasum":"16b714b116705b794553f5d0f61aee75d4c12543"},{"_id":"public/categories/swak4Foam/index.html","modified":1463930482082,"shasum":"49e1b7587dc53fb97dfa01ff4e343b720b327838"},{"_id":"public/categories/C/index.html","modified":1463930482091,"shasum":"cf665ad0b24a6f3ffc39424465970c908600646b"},{"_id":"public/categories/Paraview/index.html","modified":1463930482104,"shasum":"8561cf9362f3ca0c9e93f5d47d6c05715317bb07"},{"_id":"public/categories/DEM/index.html","modified":1463930482112,"shasum":"87e8978bf3ec4b6d00b13a48faa95ce06b90a279"},{"_id":"public/categories/test/index.html","modified":1463930482122,"shasum":"ecd8bd2717c76d88d4b2b202c154e25c4c7553a9"},{"_id":"public/index.html","modified":1463930482166,"shasum":"66f56844756832b31ac3bcc717aeda2a98e68023"},{"_id":"public/page/2/index.html","modified":1463930482207,"shasum":"33199d82f6b01e8115d313af90e4208410e06783"},{"_id":"public/page/3/index.html","modified":1463930482252,"shasum":"fa95ff0867d1a0cb9556d9793268a31cd7886702"},{"_id":"public/page/4/index.html","modified":1463930482290,"shasum":"8222a41ade23a441e8e5f42abe40d527c3976ca2"},{"_id":"public/page/5/index.html","modified":1463930482304,"shasum":"d434247b3de582c96afd0039128643581913ce16"},{"_id":"public/sitemap.xml","modified":1464771949891,"shasum":"69a1ae17aadf6ade3fee009336a7b5b603f501d9"},{"_id":"public/tags/Boundary-conditions/index.html","modified":1463930482320,"shasum":"89418b78f4668fd0e4afb0b46e2f99dc1f050ba2"},{"_id":"public/tags/wall-functions/index.html","modified":1463930482332,"shasum":"70eab03b6ea1f23de4e7c59f11103fc05c892f74"},{"_id":"public/tags/Code-Explained/index.html","modified":1463930482382,"shasum":"a9720096faaaac3f3cb4568abe51ca3fac06c750"},{"_id":"public/tags/OpenFOAM/index.html","modified":1463930482420,"shasum":"d6421c181922858a20db62d91fb33d9e59508d8b"},{"_id":"public/tags/vim/index.html","modified":1463930482426,"shasum":"944d0b5c73f645ac021e7fc74925dbb2d8823bbc"},{"_id":"public/tags/TIL/index.html","modified":1463930482435,"shasum":"850b75d921eb64585506b1936b7ea9e32fe3a0aa"},{"_id":"public/tags/groovyBC/index.html","modified":1463930482442,"shasum":"eb246e2e680d1773d3bdcf34816fb52e253c0688"},{"_id":"public/tags/C/index.html","modified":1463930482452,"shasum":"24ec105b4ce8d3eaf683bfb3c48dd81d98fd158e"},{"_id":"public/tags/paraview/index.html","modified":1463930482459,"shasum":"5d4b276df497233e392505d2cca0c2c41034398f"},{"_id":"public/tags/Postprocessing/index.html","modified":1463930482473,"shasum":"d9103e4deba862e3fee142768ebe5390ffa06195"},{"_id":"public/tags/LIGGGHTS/index.html","modified":1463930482487,"shasum":"9dc6e8d16579730437406b55bc8fc68249d8ea1a"},{"_id":"public/tags/test/index.html","modified":1463930482496,"shasum":"12f034396282d9faff11dbbedc5160292126b0e6"},{"_id":"public/tags/fvOptions/index.html","modified":1463930482506,"shasum":"d9fd728bdba472de5ac48989429caf8663e6290c"},{"_id":"public/tags/turbulence-model/index.html","modified":1463930482524,"shasum":"b39274b4fea3047410032f2cb00056b55b96fa32"},{"_id":"public/tags/Preprocessing/index.html","modified":1463930482531,"shasum":"d0c18581758626e550a4e8756cb5db81f5332d06"},{"_id":"public/tags/RTS/index.html","modified":1463930482541,"shasum":"f44ee511904a00542e7be083802f823b62eb704a"},{"_id":"public/tags/Windows/index.html","modified":1463930482549,"shasum":"f02e64bf54dc12fb9107e4dde96f5ea2ed4730e4"},{"_id":"public/tags/CentOS/index.html","modified":1463930482556,"shasum":"6cd6c995e87cfd020011a5534fb79ac70dff143e"}],"Category":[{"name":"OpenFOAM","_id":"cioiqeg970001z8mb9qlg9hfq"},{"name":"vim","_id":"cioiqegb5000pz8mbolorx8or"},{"name":"swak4Foam","_id":"cioiqegbx001gz8mb8w5905p5"},{"name":"C++","_id":"cioiqegc7001oz8mbomkrb4uy"},{"name":"Paraview","_id":"cioiqegcf001uz8mbzssncjjv"},{"name":"DEM","_id":"cioiqegcm0021z8mb9ajjzmyc"},{"name":"test","_id":"cioiqegd6002ez8mbzdcbpr46"}],"Data":[],"Page":[{"title":"About me","date":"2015-12-07T04:03:24.000Z","_content":"\n学历：博士在读\n培养单位：中国科学院过程工程研究所\n研究方向：气固流化床；介尺度结构\n联系方式：q.giskard@gmail.com\n","source":"about/index.md","raw":"title: \"About me\"\ndate: 2015-12-07 12:03:24\n\n---\n\n学历：博士在读\n培养单位：中国科学院过程工程研究所\n研究方向：气固流化床；介尺度结构\n联系方式：q.giskard@gmail.com\n","updated":"2016-04-25T02:40:43.575Z","path":"about/index.html","comments":1,"layout":"page","_id":"cioiqega00005z8mbqa9t6k76"}],"Post":[{"title":"OpenFOAM 中的边界条件（一）","date":"2016-04-02T07:29:24.000Z","comments":1,"_content":"\n本系列解读 OpenFOAM 中边界条件的实现。主要关心一些几个问题：\n1. OpenFOAM 中边界条件是怎样与有限体积离散部分交互的？\n2. 怎么从代码看懂一个边界条件具体是怎么计算边界上的值的？\n3. 怎么定制一个边界条件？\n\n本篇先阐述第一个问题。\n\n<!--more-->\n\n从有限体积离散的角度来看，离散过程中，可能要用到的边界信息包括两类：一是某个场在边界上的值，另一是某个场在边界上的梯度。前者在对流项的离散中需要用到，举例说\n$$\n\\int\\_v \\nabla \\cdot (\\rho \\mathbf{U} \\phi) dV = \\sum\\_f m\\_f \\phi\\_f\n$$\n当组成体积元的面中有边界面时，需要用到这些边界面上的值 $\\phi_f$。\n而在扩散项的离散过程中\n$$\n\\int\\_v \\nabla \\cdot (\\Gamma \\nabla \\phi) dV = \\sum\\_f (\\Gamma \\nabla \\phi)\\_f \\cdot \\mathbf{S}\\_f \n$$\n这时，如果组成体积元的某个面是边界面，就需要该边界面上 $\\phi$ 的梯度值 $\\nabla \\phi\\_f$ 了。\n\n边界上的某个场值，或者梯度值，其计算方法可以用如下通式表示\n$$\n\\begin{align}\n\\phi\\_f & = A\\_1 \\phi\\_C + B\\_1 \\\\\\\\\n\\nabla \\phi\\_f & = A\\_2 \\phi\\_C + B\\_2\n\\end{align}\n$$\n这里，$ \\phi\\_C$ 表示 $\\phi$ 在邻近边界的网格中心的值，$A\\_1$ ，$B\\_1$，$A\\_2$，$B\\_2$ 是系数。\n\nOpenFOAM 中的边界条件类中，有四个函数分别对应上面四个系数：`valueInternalCoeffs` 对应 $A\\_1$，`valueBoundaryCoeffs` 对应 $B\\_1$，`gradientInternalCoeffs` 对应 $A\\_2$，`gradientBoundaryCoeffs` 对应 $B\\_2$。\n所以，看懂 OpenFOAM 中的边界条件，很关键的一步就是看懂这四个函数的定义。\n\n此外，还有一个函数，`updateCoeffs`， 也很重要。这个函数负责对边界条件进行显式地更新。浏览一下 OpenFOAM 边界条件的代码，会发现很多边界条件都是在 `updateCoeffs` 这个函数中进行边界值的计算的。\n\n另外，还有些边界条件，似乎是在 `evaluate` 函数中进行边界值的指定的。\n\n至于边界条件是在什么地方调用的，cfd-online 上有[一个帖子](http://www.cfd-online.com/Forums/openfoam-programming-development/129271-how-boundary-conditions-called-openfoam-solvers.html)，Hrvoje Jasak 对这个问题的回答是：\n“\nEasy:\n\\- on correctBoundaryConditions() for a field\n\\- on updateCoeffs() at matrix creation\ncorrectBoundaryConditions is also called after the linear solver call automatically.\n”\n其他网友还提供了一些有价值的信息，比如，在 `correctBoundaryConditions` 函数中，\n```\ntemplate<class Type, template<class> class PatchField, class GeoMesh>\nvoid Foam::GeometricField<Type, PatchField, GeoMesh>::\ncorrectBoundaryConditions()\n{\n    this->setUpToDate();\n    storeOldTimes();\n    boundaryField_.evaluate();\n}\n```\n调用了 `evaluate` 函数。\n为什么有些边界条件用 `updateCoeffs()`，而有些则用 `evaluate()` 呢？目前的理解是这样的： `updateCoeffs()` 主要用来显式地计算并更新变量在边界上的值，当边界上的值是通过某个依赖于外部参数的公式来计算，并且值会随着迭代的进行而不断改变时，则需要用 `updateCoeffs()`。有些边界条件，比如最基本的 `zeroGradient()`，不需要外部的参数，只需要每一次将临近网格的值赋给边界就可以了，这时就可以用 `evaluate()`。\n\n关于边界条件调用的具体过程，需要在看了 `fvMatrix` 类以后才能更深入地理解，博主目前只能给出一个粗浅的理解。\n\n**参考资料**：\nThe Finite Volume Method in Computational Fluid Dynamics: An Advanced Introduction with OpenFOAM® and Matlab®\n","source":"_posts/Boundary-conditions-in-OpenFOAM1.md","raw":"title: \"OpenFOAM 中的边界条件（一）\"\ndate: 2016-04-02 15:29:24\ncomments: true\ntags:\n- Boundary conditions\ncategories:\n- OpenFOAM\n---\n\n本系列解读 OpenFOAM 中边界条件的实现。主要关心一些几个问题：\n1. OpenFOAM 中边界条件是怎样与有限体积离散部分交互的？\n2. 怎么从代码看懂一个边界条件具体是怎么计算边界上的值的？\n3. 怎么定制一个边界条件？\n\n本篇先阐述第一个问题。\n\n<!--more-->\n\n从有限体积离散的角度来看，离散过程中，可能要用到的边界信息包括两类：一是某个场在边界上的值，另一是某个场在边界上的梯度。前者在对流项的离散中需要用到，举例说\n$$\n\\int\\_v \\nabla \\cdot (\\rho \\mathbf{U} \\phi) dV = \\sum\\_f m\\_f \\phi\\_f\n$$\n当组成体积元的面中有边界面时，需要用到这些边界面上的值 $\\phi_f$。\n而在扩散项的离散过程中\n$$\n\\int\\_v \\nabla \\cdot (\\Gamma \\nabla \\phi) dV = \\sum\\_f (\\Gamma \\nabla \\phi)\\_f \\cdot \\mathbf{S}\\_f \n$$\n这时，如果组成体积元的某个面是边界面，就需要该边界面上 $\\phi$ 的梯度值 $\\nabla \\phi\\_f$ 了。\n\n边界上的某个场值，或者梯度值，其计算方法可以用如下通式表示\n$$\n\\begin{align}\n\\phi\\_f & = A\\_1 \\phi\\_C + B\\_1 \\\\\\\\\n\\nabla \\phi\\_f & = A\\_2 \\phi\\_C + B\\_2\n\\end{align}\n$$\n这里，$ \\phi\\_C$ 表示 $\\phi$ 在邻近边界的网格中心的值，$A\\_1$ ，$B\\_1$，$A\\_2$，$B\\_2$ 是系数。\n\nOpenFOAM 中的边界条件类中，有四个函数分别对应上面四个系数：`valueInternalCoeffs` 对应 $A\\_1$，`valueBoundaryCoeffs` 对应 $B\\_1$，`gradientInternalCoeffs` 对应 $A\\_2$，`gradientBoundaryCoeffs` 对应 $B\\_2$。\n所以，看懂 OpenFOAM 中的边界条件，很关键的一步就是看懂这四个函数的定义。\n\n此外，还有一个函数，`updateCoeffs`， 也很重要。这个函数负责对边界条件进行显式地更新。浏览一下 OpenFOAM 边界条件的代码，会发现很多边界条件都是在 `updateCoeffs` 这个函数中进行边界值的计算的。\n\n另外，还有些边界条件，似乎是在 `evaluate` 函数中进行边界值的指定的。\n\n至于边界条件是在什么地方调用的，cfd-online 上有[一个帖子](http://www.cfd-online.com/Forums/openfoam-programming-development/129271-how-boundary-conditions-called-openfoam-solvers.html)，Hrvoje Jasak 对这个问题的回答是：\n“\nEasy:\n\\- on correctBoundaryConditions() for a field\n\\- on updateCoeffs() at matrix creation\ncorrectBoundaryConditions is also called after the linear solver call automatically.\n”\n其他网友还提供了一些有价值的信息，比如，在 `correctBoundaryConditions` 函数中，\n```\ntemplate<class Type, template<class> class PatchField, class GeoMesh>\nvoid Foam::GeometricField<Type, PatchField, GeoMesh>::\ncorrectBoundaryConditions()\n{\n    this->setUpToDate();\n    storeOldTimes();\n    boundaryField_.evaluate();\n}\n```\n调用了 `evaluate` 函数。\n为什么有些边界条件用 `updateCoeffs()`，而有些则用 `evaluate()` 呢？目前的理解是这样的： `updateCoeffs()` 主要用来显式地计算并更新变量在边界上的值，当边界上的值是通过某个依赖于外部参数的公式来计算，并且值会随着迭代的进行而不断改变时，则需要用 `updateCoeffs()`。有些边界条件，比如最基本的 `zeroGradient()`，不需要外部的参数，只需要每一次将临近网格的值赋给边界就可以了，这时就可以用 `evaluate()`。\n\n关于边界条件调用的具体过程，需要在看了 `fvMatrix` 类以后才能更深入地理解，博主目前只能给出一个粗浅的理解。\n\n**参考资料**：\nThe Finite Volume Method in Computational Fluid Dynamics: An Advanced Introduction with OpenFOAM® and Matlab®\n","slug":"Boundary-conditions-in-OpenFOAM1","published":1,"updated":"2016-04-03T07:27:24.175Z","layout":"post","photos":[],"link":"","_id":"cioiqeg8v0000z8mboca1zeg7"},{"title":"OpenFOAM 中的壁面函数（四）","date":"2016-04-24T16:43:40.000Z","_content":"\n这篇来看看可能是最关键的 $\\nu\\_t$ 的壁面函数。\n\n<!--more-->\n\n\n##### 5. 湍流粘度 $\\nu_t$ 的壁面函数\n这个类型的壁面函数，结构比较简单，计算的是每一个壁面边界面上的湍流粘度 $\\nu_t$。\n`nutWallFunction` 是虚基类，其中定义了一个纯虚函数 `calcNut`\n```\nvirtual tmp<scalarField> calcNut() const = 0;\n```\n并且在 `updateCoeffs` 函数中，将 `calcNut` 的返回值赋值给边界面\n```\nvoid nutWallFunctionFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    operator==(calcNut());\n\n    fixedValueFvPatchScalarField::updateCoeffs();\n}\n```\n这样，在具体的那些计算 $\\nu\\_t$ 的壁面函数中，只需要看 `calcNut` 的返回值就可以了。\n+ (1). nutkWallFunction\n```\ntmp<scalarField> nutkWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const tmp<volScalarField> tk = turbModel.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    const scalar Cmu25 = pow025(Cmu_);\n    tmp<scalarField> tnutw(new scalarField(patch().size(), 0.0));\n    scalarField& nutw = tnutw();\n    forAll(nutw, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar yPlus = Cmu25*y[faceI]*sqrt(k[faceCellI])/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            nutw[faceI] = nuw[faceI]*(yPlus*kappa_/log(E_*yPlus) - 1.0);\n        }\n    }\n    return tnutw;\n}\n```\n\n这里，仍然是分情况处理\n`yPlus < yPlusLam_` 时，壁面上的 `nut` 设为0；\n`yPlus > yPlusLam_` 时\n$$\n\\nu\\_t = \\nu \\cdot \\left( \\frac{\\kappa y^+}{\\ln(Ey^+)}-1 \\right)\n$$\n这里实现的其实就是标准壁面函数。理论上讲，这里的计算只在粘性底层和对数区是有效的，所以，使用这个壁面条件的时候，要尽量壁面网格落在过渡区，否则可能会引入较大误差。\n\n顺带提一下，这里还定义了一个 `yPlus` 函数，用来计算 $y^+$，这个函数在这里没有调用，不过在其他代码中需要 $y^+$ 的时候会调用这个函数。比如，计算 $y^+$ 的应用 `yPlusRAS` 就是调用这里的 `yPlus` 函数来计算 $y^+$。\n```\ntmp<scalarField> nutkWallFunctionFvPatchScalarField::yPlus() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n\n    const tmp<volScalarField> tk = turbModel.k();\n    const volScalarField& k = tk();\n    tmp<scalarField> kwc = k.boundaryField()[patchi].patchInternalField();\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n\n    return pow025(Cmu_)*y*sqrt(kwc)/nuw;\n}\n```\n\n+ (2). nutUWallFunction\n```\ntmp<scalarField> nutUWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchi];\n    const scalarField magUp(mag(Uw.patchInternalField() - Uw));\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    tmp<scalarField> tyPlus = calcYPlus(magUp);\n    scalarField& yPlus = tyPlus();\n    tmp<scalarField> tnutw(new scalarField(patch().size(), 0.0));\n    scalarField& nutw = tnutw();\n\n    forAll(yPlus, facei)\n    {\n        if (yPlus[facei] > yPlusLam_)\n        {\n            nutw[facei] =\n                nuw[facei]*(yPlus[facei]*kappa_/log(E_*yPlus[facei]) - 1.0);\n        }\n    }\n    return tnutw;\n}\n\ntmp<scalarField> nutUWallFunctionFvPatchScalarField::calcYPlus\n(\n    const scalarField& magUp\n) const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    tmp<scalarField> tyPlus(new scalarField(patch().size(), 0.0));\n    scalarField& yPlus = tyPlus();\n    forAll(yPlus, facei)\n    {\n        scalar kappaRe = kappa_*magUp[facei]*y[facei]/nuw[facei];\n        scalar yp = yPlusLam_;\n        scalar ryPlusLam = 1.0/yp;\n        int iter = 0;\n        scalar yPlusLast = 0.0;\n        do\n        {\n            yPlusLast = yp;\n            yp = (kappaRe + yp)/(1.0 + log(E_*yp));\n\n        } while (mag(ryPlusLam*(yp - yPlusLast)) > 0.01 && ++iter < 10 );\n\n        yPlus[facei] = max(0.0, yp);\n    }\n    return tyPlus;\n}\n```\n这个壁函数的 $y^+$ 的计算方式跟 `nutkWallFunction` 有点区别。经过摸索，这里 `calcYPlus` 函数中的那段 `do ... while` 循环的原理如下：\n对数律可以表达如下：\n$$\nU^+ = \\frac{U\\_p}{u\\_\\tau}=\\frac{1}{\\kappa}\\ln(Ey^+)\n$$\n其中 $U\\_p$ 等于壁面上的速度减去壁面所属网格中心的速度。\n经过简单变形\n$$\n\\frac{U\\_p}{ y u\\_\\tau/\\nu }\\cdot (y/\\nu)=\\frac{U\\_p}{ y^+}\\cdot (y/\\nu)=\\frac{1}{\\kappa}\\ln(Ey^+)\n$$\n整理得\n$$\ny^+ \\ln(Ey^+) - \\frac{\\kappa y U\\_p}{\\nu}=0\n$$\n这是一个 $y^+$ 的一元方程，可以通过牛顿迭代来求解\n$$\ny^+\\_{n+1} = y^+\\_{n} - \\frac{f(y^+)}{f^{\\prime}(y+)} = y^+\\_{n}-\\frac{y\\_n^+ \\ln(Ey\\_n^+) - \\frac{\\kappa y U\\_p}{\\nu}}{1+\\ln(Ey\\_n^+)} = \\frac{y\\_n^+ + \\frac{\\kappa y U\\_p}{\\nu}}{1+\\ln(Ey\\_n^+)}\n$$\n上面代码里的 `do ... while` 循环，正是在做这个迭代求解，初始值选择的是 `yPlusLam`，这个值在前面提过了。 \n求出 $y^+$ 以后，$\\nu\\_t$ 计算如下\n$$\n\\nu\\_t = \\nu \\cdot \\left( \\frac{\\kappa y^+}{\\ln(Ey^+)}-1 \\right)\n$$\n与 `nutkWallFunction` 形式是一样的。\n\n这个壁面函数，求壁面上的 $\\nu_t$ 时使用的对数律方程，所以，理论上这个壁面函数应该只适用于第一层网格落在对数层的情形。\n\n+ (3). nutLowReWallFunction\n这个壁面函数直接将壁面上的 $\\nu\\_t$ 的值设为0。\n```\ntmp<scalarField> nutLowReWallFunctionFvPatchScalarField::calcNut() const\n{\n    return tmp<scalarField>(new scalarField(patch().size(), 0.0));\n}\n```\n$y^+$ 的计算也值得注意：\n```\ntmp<scalarField> nutLowReWallFunctionFvPatchScalarField::yPlus() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchi];\n\n    return y*sqrt(nuw*mag(Uw.snGrad()))/nuw;\n}\n```\n$$\ny^+ = \\frac{y\\sqrt{\\nu \\cdot |\\frac{U\\_w-U\\_c}{d}|}}{\\nu}\n$$\n注意由于 $\\nu\\_t = 0$ ，所以 $\\frac{\\tau\\_w}{\\rho} = \\nu \\cdot |\\frac{U\\_w-U\\_c}{d}|$，所以，$\\sqrt{\\nu \\cdot |\\frac{U\\_w-U\\_c}{d}|}=\\sqrt{\\frac{\\tau\\_w}{\\rho}}=u\\_\\tau$ 。\n\n+ (4). nutUSpaldingWallFunction\n这个壁函数基于 Spalding 提出的一个拟合的 $y^+$ 与 $u^+$ 的关系式，见文献 *A Single Formula for the “Law of the Wall” * 。\n$$\ny^+ = u^+ + \\frac{1}{E}\\left[ e^{\\kappa u^+} -1-\\kappa u^+ -\\frac{1}{2}(\\kappa u^+)^2 - \\frac{1}{6}(\\kappa u^+)^3 \\right]\n$$\n```\ntmp<scalarField> nutUSpaldingWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchI = patch().index();\n\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchI];\n    const scalarField magGradU(mag(Uw.snGrad()));\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchI];\n\n    return max\n    (\n        scalar(0),\n        sqr(calcUTau(magGradU))/(magGradU + ROOTVSMALL) - nuw\n    );\n}\n\ntmp<scalarField> nutUSpaldingWallFunctionFvPatchScalarField::calcUTau\n(\n    const scalarField& magGradU\n) const\n{\n    const label patchI = patch().index();\n\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchI];\n\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchI];\n    const scalarField magUp(mag(Uw.patchInternalField() - Uw));\n\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchI];\n\n    const scalarField& nutw = *this;\n\n    tmp<scalarField> tuTau(new scalarField(patch().size(), 0.0));\n    scalarField& uTau = tuTau();\n\n    forAll(uTau, faceI)\n    {\n        scalar ut = sqrt((nutw[faceI] + nuw[faceI])*magGradU[faceI]);\n\n        if (ut > ROOTVSMALL)\n        {\n            int iter = 0;\n            scalar err = GREAT;\n\n            do\n            {\n                scalar kUu = min(kappa_*magUp[faceI]/ut, 50);\n                scalar fkUu = exp(kUu) - 1 - kUu*(1 + 0.5*kUu);\n\n                scalar f =\n                    - ut*y[faceI]/nuw[faceI]\n                    + magUp[faceI]/ut\n                    + 1/E_*(fkUu - 1.0/6.0*kUu*sqr(kUu));\n\n                scalar df =\n                    y[faceI]/nuw[faceI]\n                  + magUp[faceI]/sqr(ut)\n                  + 1/E_*kUu*fkUu/ut;\n\n                scalar uTauNew = ut + f/df;\n                err = mag((ut - uTauNew)/ut);\n                ut = uTauNew;\n\n            } while (ut > ROOTVSMALL && err > 0.01 && ++iter < 10);\n\n            uTau[faceI] = max(0.0, ut);\n        }\n    }\n    return tuTau;\n}\n```\n`calcUtau` 函数，其实是在用牛顿法迭代求解 $y^+$，进而得到 $u\\_\\tau$ 的值。`calcNut` 函数中\n$$\n\\frac{u\\_\\tau ^2}{|\\frac{U\\_w-U\\_c}{d}|} - \\nu = \\frac{\\tau\\_w}{|\\frac{U\\_w-U\\_c}{d}|} -\\nu = \\nu\\_{eff} - \\nu = \\nu\\_t\n$$\n\n这个壁面函数使用的是从粘性底层连续变化到对数层的 $y^+ \\text{-} u^+$ 关系式，所以，这个可以认为是网格无关的，即不管第一层网格落在哪个区，都是有效的。如果网格无法做到全部位于粘性层或者对数区，建议用这个壁面条件。\n\n+ (5). nutUTabulatedWallFunction\n\n这个壁面函数，需要从外部读取一个 $U^+ \\text{-}\\,Re\\_y$ 数据表，通过从这个数据表插值来得到 $U^+$ 的值。其中 $Re\\_y=yU/\\nu$ 。\n```\n// 构造函数\nnutUTabulatedWallFunctionFvPatchScalarField::\nnutUTabulatedWallFunctionFvPatchScalarField\n(\n    const fvPatch& p,\n    const DimensionedField<scalar, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    nutWallFunctionFvPatchScalarField(p, iF, dict),\n    uPlusTableName_(dict.lookup(\"uPlusTable\")),\n    uPlusTable_\n    (\n        IOobject\n        (\n            uPlusTableName_,\n            patch().boundaryMesh().mesh().time().constant(),\n            patch().boundaryMesh().mesh(),\n            IOobject::MUST_READ_IF_MODIFIED,\n            IOobject::NO_WRITE,\n            false\n        ),\n        true\n    )\n{}\n```\n\n$U^+$ 和 $\\nu\\_t$ 分别由函数 `calcUPlus` 和 `calcNut` 来计算。\n\n```\ntmp<scalarField> nutUTabulatedWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchi];\n    const scalarField magUp(mag(Uw.patchInternalField() - Uw));\n    const scalarField magGradU(mag(Uw.snGrad()));\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    return\n        max\n        (\n            scalar(0),\n            sqr(magUp/(calcUPlus(magUp*y/nuw) + ROOTVSMALL))\n           /(magGradU + ROOTVSMALL)\n          - nuw\n        );\n    // magUp/UPlus = utau, sqr(utau) = tauw, tauw/magGradU = nuEff = nut + nu\n}\n\ntmp<scalarField> nutUTabulatedWallFunctionFvPatchScalarField::calcUPlus\n(\n    const scalarField& Rey\n) const\n{\n    tmp<scalarField> tuPlus(new scalarField(patch().size(), 0.0));\n    scalarField& uPlus = tuPlus();\n    forAll(uPlus, faceI)\n    {\n        uPlus[faceI] = uPlusTable_.interpolateLog10(Rey[faceI]);\n    }\n    return tuPlus;\n}\n```\n注意这里 `calcUPlus` 用的是 `interpolateLog10` 函数来插值，这个函数的定义为\n```\ntemplate<class Type>\nType Foam::uniformInterpolationTable<Type>::interpolateLog10\n(\n    scalar x\n) const\n{\n    if (log10_)\n    {\n        if (x > 0)\n        {\n            x = ::log10(x);\n        }\n        else if (bound_ && (x <= 0))\n        {\n            x = x0_;\n        }\n        else\n        {\n            FatalErrorIn\n            (\n                \"uniformInterpolationTable<Type>::interpolateLog10(scalar x)\"\n            )   << \"Table \" << name() << nl\n                << \"Supplied value must be greater than 0 when in log10 mode\"\n                << nl << \"x=\" << x << nl << exit(FatalError);\n        }\n    }\n    return interpolate(x); // 这个是普通的线性插值函数\n}\n```\n即计算 `x` 的对数（log10），在将计算结果用来进行线性插值。所以，用这个壁面函数的时候，要注意你所提供的数据表是普通线性坐标的还是对数坐标的。\n\n基本上常见的处理壁面上的湍流粘度的方法就是以上几种了。OpenFOAM 中还提供了几个能处理粗糙壁面的壁面函数( `nutURoughWallFunction` ， `nutkRoughWallFunction` )，以及处理大气层边界的(`nutkAtmRoughWallFunction`，需要跟 `atmBoundaryLayerInletVelocity` 这个入口边界配合使用 )，细节这里不再详述了，有需要时可以去看相关代码，代码结构是类似的，只是具体计算公式不一样。\n","source":"_posts/wallFunctions4.md","raw":"title: \"OpenFOAM 中的壁面函数（四）\"\ndate: 2016-04-25 00:43:40\ntags:\n- wall functions\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n这篇来看看可能是最关键的 $\\nu\\_t$ 的壁面函数。\n\n<!--more-->\n\n\n##### 5. 湍流粘度 $\\nu_t$ 的壁面函数\n这个类型的壁面函数，结构比较简单，计算的是每一个壁面边界面上的湍流粘度 $\\nu_t$。\n`nutWallFunction` 是虚基类，其中定义了一个纯虚函数 `calcNut`\n```\nvirtual tmp<scalarField> calcNut() const = 0;\n```\n并且在 `updateCoeffs` 函数中，将 `calcNut` 的返回值赋值给边界面\n```\nvoid nutWallFunctionFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    operator==(calcNut());\n\n    fixedValueFvPatchScalarField::updateCoeffs();\n}\n```\n这样，在具体的那些计算 $\\nu\\_t$ 的壁面函数中，只需要看 `calcNut` 的返回值就可以了。\n+ (1). nutkWallFunction\n```\ntmp<scalarField> nutkWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const tmp<volScalarField> tk = turbModel.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    const scalar Cmu25 = pow025(Cmu_);\n    tmp<scalarField> tnutw(new scalarField(patch().size(), 0.0));\n    scalarField& nutw = tnutw();\n    forAll(nutw, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar yPlus = Cmu25*y[faceI]*sqrt(k[faceCellI])/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            nutw[faceI] = nuw[faceI]*(yPlus*kappa_/log(E_*yPlus) - 1.0);\n        }\n    }\n    return tnutw;\n}\n```\n\n这里，仍然是分情况处理\n`yPlus < yPlusLam_` 时，壁面上的 `nut` 设为0；\n`yPlus > yPlusLam_` 时\n$$\n\\nu\\_t = \\nu \\cdot \\left( \\frac{\\kappa y^+}{\\ln(Ey^+)}-1 \\right)\n$$\n这里实现的其实就是标准壁面函数。理论上讲，这里的计算只在粘性底层和对数区是有效的，所以，使用这个壁面条件的时候，要尽量壁面网格落在过渡区，否则可能会引入较大误差。\n\n顺带提一下，这里还定义了一个 `yPlus` 函数，用来计算 $y^+$，这个函数在这里没有调用，不过在其他代码中需要 $y^+$ 的时候会调用这个函数。比如，计算 $y^+$ 的应用 `yPlusRAS` 就是调用这里的 `yPlus` 函数来计算 $y^+$。\n```\ntmp<scalarField> nutkWallFunctionFvPatchScalarField::yPlus() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n\n    const tmp<volScalarField> tk = turbModel.k();\n    const volScalarField& k = tk();\n    tmp<scalarField> kwc = k.boundaryField()[patchi].patchInternalField();\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n\n    return pow025(Cmu_)*y*sqrt(kwc)/nuw;\n}\n```\n\n+ (2). nutUWallFunction\n```\ntmp<scalarField> nutUWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchi];\n    const scalarField magUp(mag(Uw.patchInternalField() - Uw));\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    tmp<scalarField> tyPlus = calcYPlus(magUp);\n    scalarField& yPlus = tyPlus();\n    tmp<scalarField> tnutw(new scalarField(patch().size(), 0.0));\n    scalarField& nutw = tnutw();\n\n    forAll(yPlus, facei)\n    {\n        if (yPlus[facei] > yPlusLam_)\n        {\n            nutw[facei] =\n                nuw[facei]*(yPlus[facei]*kappa_/log(E_*yPlus[facei]) - 1.0);\n        }\n    }\n    return tnutw;\n}\n\ntmp<scalarField> nutUWallFunctionFvPatchScalarField::calcYPlus\n(\n    const scalarField& magUp\n) const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    tmp<scalarField> tyPlus(new scalarField(patch().size(), 0.0));\n    scalarField& yPlus = tyPlus();\n    forAll(yPlus, facei)\n    {\n        scalar kappaRe = kappa_*magUp[facei]*y[facei]/nuw[facei];\n        scalar yp = yPlusLam_;\n        scalar ryPlusLam = 1.0/yp;\n        int iter = 0;\n        scalar yPlusLast = 0.0;\n        do\n        {\n            yPlusLast = yp;\n            yp = (kappaRe + yp)/(1.0 + log(E_*yp));\n\n        } while (mag(ryPlusLam*(yp - yPlusLast)) > 0.01 && ++iter < 10 );\n\n        yPlus[facei] = max(0.0, yp);\n    }\n    return tyPlus;\n}\n```\n这个壁函数的 $y^+$ 的计算方式跟 `nutkWallFunction` 有点区别。经过摸索，这里 `calcYPlus` 函数中的那段 `do ... while` 循环的原理如下：\n对数律可以表达如下：\n$$\nU^+ = \\frac{U\\_p}{u\\_\\tau}=\\frac{1}{\\kappa}\\ln(Ey^+)\n$$\n其中 $U\\_p$ 等于壁面上的速度减去壁面所属网格中心的速度。\n经过简单变形\n$$\n\\frac{U\\_p}{ y u\\_\\tau/\\nu }\\cdot (y/\\nu)=\\frac{U\\_p}{ y^+}\\cdot (y/\\nu)=\\frac{1}{\\kappa}\\ln(Ey^+)\n$$\n整理得\n$$\ny^+ \\ln(Ey^+) - \\frac{\\kappa y U\\_p}{\\nu}=0\n$$\n这是一个 $y^+$ 的一元方程，可以通过牛顿迭代来求解\n$$\ny^+\\_{n+1} = y^+\\_{n} - \\frac{f(y^+)}{f^{\\prime}(y+)} = y^+\\_{n}-\\frac{y\\_n^+ \\ln(Ey\\_n^+) - \\frac{\\kappa y U\\_p}{\\nu}}{1+\\ln(Ey\\_n^+)} = \\frac{y\\_n^+ + \\frac{\\kappa y U\\_p}{\\nu}}{1+\\ln(Ey\\_n^+)}\n$$\n上面代码里的 `do ... while` 循环，正是在做这个迭代求解，初始值选择的是 `yPlusLam`，这个值在前面提过了。 \n求出 $y^+$ 以后，$\\nu\\_t$ 计算如下\n$$\n\\nu\\_t = \\nu \\cdot \\left( \\frac{\\kappa y^+}{\\ln(Ey^+)}-1 \\right)\n$$\n与 `nutkWallFunction` 形式是一样的。\n\n这个壁面函数，求壁面上的 $\\nu_t$ 时使用的对数律方程，所以，理论上这个壁面函数应该只适用于第一层网格落在对数层的情形。\n\n+ (3). nutLowReWallFunction\n这个壁面函数直接将壁面上的 $\\nu\\_t$ 的值设为0。\n```\ntmp<scalarField> nutLowReWallFunctionFvPatchScalarField::calcNut() const\n{\n    return tmp<scalarField>(new scalarField(patch().size(), 0.0));\n}\n```\n$y^+$ 的计算也值得注意：\n```\ntmp<scalarField> nutLowReWallFunctionFvPatchScalarField::yPlus() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchi];\n\n    return y*sqrt(nuw*mag(Uw.snGrad()))/nuw;\n}\n```\n$$\ny^+ = \\frac{y\\sqrt{\\nu \\cdot |\\frac{U\\_w-U\\_c}{d}|}}{\\nu}\n$$\n注意由于 $\\nu\\_t = 0$ ，所以 $\\frac{\\tau\\_w}{\\rho} = \\nu \\cdot |\\frac{U\\_w-U\\_c}{d}|$，所以，$\\sqrt{\\nu \\cdot |\\frac{U\\_w-U\\_c}{d}|}=\\sqrt{\\frac{\\tau\\_w}{\\rho}}=u\\_\\tau$ 。\n\n+ (4). nutUSpaldingWallFunction\n这个壁函数基于 Spalding 提出的一个拟合的 $y^+$ 与 $u^+$ 的关系式，见文献 *A Single Formula for the “Law of the Wall” * 。\n$$\ny^+ = u^+ + \\frac{1}{E}\\left[ e^{\\kappa u^+} -1-\\kappa u^+ -\\frac{1}{2}(\\kappa u^+)^2 - \\frac{1}{6}(\\kappa u^+)^3 \\right]\n$$\n```\ntmp<scalarField> nutUSpaldingWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchI = patch().index();\n\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchI];\n    const scalarField magGradU(mag(Uw.snGrad()));\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchI];\n\n    return max\n    (\n        scalar(0),\n        sqr(calcUTau(magGradU))/(magGradU + ROOTVSMALL) - nuw\n    );\n}\n\ntmp<scalarField> nutUSpaldingWallFunctionFvPatchScalarField::calcUTau\n(\n    const scalarField& magGradU\n) const\n{\n    const label patchI = patch().index();\n\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchI];\n\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchI];\n    const scalarField magUp(mag(Uw.patchInternalField() - Uw));\n\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchI];\n\n    const scalarField& nutw = *this;\n\n    tmp<scalarField> tuTau(new scalarField(patch().size(), 0.0));\n    scalarField& uTau = tuTau();\n\n    forAll(uTau, faceI)\n    {\n        scalar ut = sqrt((nutw[faceI] + nuw[faceI])*magGradU[faceI]);\n\n        if (ut > ROOTVSMALL)\n        {\n            int iter = 0;\n            scalar err = GREAT;\n\n            do\n            {\n                scalar kUu = min(kappa_*magUp[faceI]/ut, 50);\n                scalar fkUu = exp(kUu) - 1 - kUu*(1 + 0.5*kUu);\n\n                scalar f =\n                    - ut*y[faceI]/nuw[faceI]\n                    + magUp[faceI]/ut\n                    + 1/E_*(fkUu - 1.0/6.0*kUu*sqr(kUu));\n\n                scalar df =\n                    y[faceI]/nuw[faceI]\n                  + magUp[faceI]/sqr(ut)\n                  + 1/E_*kUu*fkUu/ut;\n\n                scalar uTauNew = ut + f/df;\n                err = mag((ut - uTauNew)/ut);\n                ut = uTauNew;\n\n            } while (ut > ROOTVSMALL && err > 0.01 && ++iter < 10);\n\n            uTau[faceI] = max(0.0, ut);\n        }\n    }\n    return tuTau;\n}\n```\n`calcUtau` 函数，其实是在用牛顿法迭代求解 $y^+$，进而得到 $u\\_\\tau$ 的值。`calcNut` 函数中\n$$\n\\frac{u\\_\\tau ^2}{|\\frac{U\\_w-U\\_c}{d}|} - \\nu = \\frac{\\tau\\_w}{|\\frac{U\\_w-U\\_c}{d}|} -\\nu = \\nu\\_{eff} - \\nu = \\nu\\_t\n$$\n\n这个壁面函数使用的是从粘性底层连续变化到对数层的 $y^+ \\text{-} u^+$ 关系式，所以，这个可以认为是网格无关的，即不管第一层网格落在哪个区，都是有效的。如果网格无法做到全部位于粘性层或者对数区，建议用这个壁面条件。\n\n+ (5). nutUTabulatedWallFunction\n\n这个壁面函数，需要从外部读取一个 $U^+ \\text{-}\\,Re\\_y$ 数据表，通过从这个数据表插值来得到 $U^+$ 的值。其中 $Re\\_y=yU/\\nu$ 。\n```\n// 构造函数\nnutUTabulatedWallFunctionFvPatchScalarField::\nnutUTabulatedWallFunctionFvPatchScalarField\n(\n    const fvPatch& p,\n    const DimensionedField<scalar, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    nutWallFunctionFvPatchScalarField(p, iF, dict),\n    uPlusTableName_(dict.lookup(\"uPlusTable\")),\n    uPlusTable_\n    (\n        IOobject\n        (\n            uPlusTableName_,\n            patch().boundaryMesh().mesh().time().constant(),\n            patch().boundaryMesh().mesh(),\n            IOobject::MUST_READ_IF_MODIFIED,\n            IOobject::NO_WRITE,\n            false\n        ),\n        true\n    )\n{}\n```\n\n$U^+$ 和 $\\nu\\_t$ 分别由函数 `calcUPlus` 和 `calcNut` 来计算。\n\n```\ntmp<scalarField> nutUTabulatedWallFunctionFvPatchScalarField::calcNut() const\n{\n    const label patchi = patch().index();\n    const turbulenceModel& turbModel =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbModel.y()[patchi];\n    const fvPatchVectorField& Uw = turbModel.U().boundaryField()[patchi];\n    const scalarField magUp(mag(Uw.patchInternalField() - Uw));\n    const scalarField magGradU(mag(Uw.snGrad()));\n    const tmp<volScalarField> tnu = turbModel.nu();\n    const volScalarField& nu = tnu();\n    const scalarField& nuw = nu.boundaryField()[patchi];\n    return\n        max\n        (\n            scalar(0),\n            sqr(magUp/(calcUPlus(magUp*y/nuw) + ROOTVSMALL))\n           /(magGradU + ROOTVSMALL)\n          - nuw\n        );\n    // magUp/UPlus = utau, sqr(utau) = tauw, tauw/magGradU = nuEff = nut + nu\n}\n\ntmp<scalarField> nutUTabulatedWallFunctionFvPatchScalarField::calcUPlus\n(\n    const scalarField& Rey\n) const\n{\n    tmp<scalarField> tuPlus(new scalarField(patch().size(), 0.0));\n    scalarField& uPlus = tuPlus();\n    forAll(uPlus, faceI)\n    {\n        uPlus[faceI] = uPlusTable_.interpolateLog10(Rey[faceI]);\n    }\n    return tuPlus;\n}\n```\n注意这里 `calcUPlus` 用的是 `interpolateLog10` 函数来插值，这个函数的定义为\n```\ntemplate<class Type>\nType Foam::uniformInterpolationTable<Type>::interpolateLog10\n(\n    scalar x\n) const\n{\n    if (log10_)\n    {\n        if (x > 0)\n        {\n            x = ::log10(x);\n        }\n        else if (bound_ && (x <= 0))\n        {\n            x = x0_;\n        }\n        else\n        {\n            FatalErrorIn\n            (\n                \"uniformInterpolationTable<Type>::interpolateLog10(scalar x)\"\n            )   << \"Table \" << name() << nl\n                << \"Supplied value must be greater than 0 when in log10 mode\"\n                << nl << \"x=\" << x << nl << exit(FatalError);\n        }\n    }\n    return interpolate(x); // 这个是普通的线性插值函数\n}\n```\n即计算 `x` 的对数（log10），在将计算结果用来进行线性插值。所以，用这个壁面函数的时候，要注意你所提供的数据表是普通线性坐标的还是对数坐标的。\n\n基本上常见的处理壁面上的湍流粘度的方法就是以上几种了。OpenFOAM 中还提供了几个能处理粗糙壁面的壁面函数( `nutURoughWallFunction` ， `nutkRoughWallFunction` )，以及处理大气层边界的(`nutkAtmRoughWallFunction`，需要跟 `atmBoundaryLayerInletVelocity` 这个入口边界配合使用 )，细节这里不再详述了，有需要时可以去看相关代码，代码结构是类似的，只是具体计算公式不一样。\n","slug":"wallFunctions4","published":1,"updated":"2016-04-25T03:14:35.521Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cioiqega40006z8mbh4ha3suv"},{"title":"OpenFOAM 中的壁面函数（三）","date":"2016-04-24T16:43:34.000Z","_content":"\n这篇来看看计算湍动能 $\\varepsilon$  和 $\\omega$ 的壁面函数。\n\n<!--more-->\n\n##### 3. 湍动能耗散 $\\varepsilon$ 的壁面函数\n本篇来看看 OpenFOAM 中的 `epsilonWallFunction`，共有两个： `epsilonWallFunction` 和 `epsilonLowReWallFunction`。\n+ (1). epsilonWallFunction\n\n`epsilonWallFunction` 代码比前面的 `kqRWallFunction` 复杂多了，主要原因在于这里需要得到的是 `epsilon` 在临近网格的值，而且，需要考虑包含两个边界面的网格。这里先来梳理代码的脉络，然后再看具体的计算细节。\n外部调用的主要是 `updateCoeffs()` 函数，所以，从这个函数看起。\n```cpp\nvoid epsilonWallFunctionFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n    const turbulenceModel& turbulence =\n        db().lookupObject<turbulenceModel>(turbulenceModel::typeName);\n\n    setMaster();\n\n    if (patch().index() == master_)\n    {\n        createAveragingWeights();\n        calculateTurbulenceFields(turbulence, G(true), epsilon(true));\n    }\n    const scalarField& G0 = this->G();\n    const scalarField& epsilon0 = this->epsilon();\n\n    typedef DimensionedField<scalar, volMesh> FieldType;\n\n    FieldType& G =\n        const_cast<FieldType&>\n        (\n            db().lookupObject<FieldType>(turbulence.GName())\n        );\n    //这里是获取内部场，所以，修改这里的引用 \"epsilon\",相当于修改 epsilon 的内部场值。\n    FieldType& epsilon = const_cast<FieldType&>(dimensionedInternalField());\n\n    forAll(*this, faceI)\n    {\n        label cellI = patch().faceCells()[faceI];\n\n        G[cellI] = G0[cellI];\n        epsilon[cellI] = epsilon0[cellI];\n    }\n    fvPatchField<scalar>::updateCoeffs();\n}\n```\n 一步一步来看。首先是调用了 `setMaster()` 函数，来看看这个函数以及相关的一个函数 `epsilonPatch` 的代码： \n```\nvoid epsilonWallFunctionFvPatchScalarField::setMaster()\n{\n    if (master_ != -1) // 如果当前处理的边界的 master_ != -1，说明它已被处理过，直接返回\n    {\n        return;\n    }\n    const volScalarField& epsilon =\n        static_cast<const volScalarField&>(this->dimensionedInternalField());\n\n    const volScalarField::GeometricBoundaryField& bf = epsilon.boundaryField();\n\n    label master = -1;\n    forAll(bf, patchI)\n    {\n        if (isA<epsilonWallFunctionFvPatchScalarField>(bf[patchI]))\n        {\n            epsilonWallFunctionFvPatchScalarField& epf = epsilonPatch(patchI);\n\n            if (master == -1) // 只有头一个被处理的边界满足这个条件\n            {\n                master = patchI;\n            }\n\n            epf.master() = master; // 这意味着所有边界的 master_ 数据成员都将赋值为头一个被处理的边界的编号，即第一个被处理的边界是master\n        }\n    }\n}\n\nepsilonWallFunctionFvPatchScalarField&\nepsilonWallFunctionFvPatchScalarField::epsilonPatch(const label patchI)\n{\n    const volScalarField& epsilon =\n        static_cast<const volScalarField&>(this->dimensionedInternalField());\n\n    const volScalarField::GeometricBoundaryField& bf = epsilon.boundaryField();\n\n    const epsilonWallFunctionFvPatchScalarField& epf =\n        refCast<const epsilonWallFunctionFvPatchScalarField>(bf[patchI]);\n\n    return const_cast<epsilonWallFunctionFvPatchScalarField&>(epf);\n}\n\n```\n 从上述代码可以看出， `epsilonPatch` 函数需要一个参数，这个参数的含义是某一个边界的序号，返回的是指向这个边界的一个 `epsilonWallFunctionFvPatchScalarField` 类型的引用。\n在此基础上，再来看 `setMaster`。先判断当前边界的数据成员 `master_` 是否不等于-1，如果成立则不做任何操作，直接返回；否则，先获取到 `epsilon` 的所有边界，存在变量 `bf` 中，然后，遍历 `bf` ，如果边界的类型是 `epsilonWallFunctionFvPatchScalarField`，则判断临时变量 `master` 是否等于 `-1`，等于则将边界的序号 `patchI`  赋值给 `master`，并临时变量 `master` 的值赋给 `patchI` 对应边界的数据成员 `master_`。 举个例子，假设有一个算例，有两个边界上使用了 `epsilonWallFunctionFvPatchScalarField` 类型的边界条件，两个边界的编号分别是 `patchI = 0` 和 `patchI = 1`。则在上述循环过程中，当 `patchI = 0`时， `master == -1` 肯定成立。于是，`patchI = 0` 对应边界的数据成员 `master_` 被赋值为0；而当遍历到 `patchI = 1` 时， 此时`master = 0`，所以，结果是 `patchI = 1` 的边界的数据成员 `master_` 也被赋值为0。 \n\n 继续向下看，如果 `patch.index() == master_` ，则调用两个函数。这个怎么理解呢？还以上面的那个简单例子来说明。注意，在外部调用边界条件的时候，也是会依次调用一个场的所有边界的边界条件的。在这里的简单例子中，有两个边界的类型是 `epsilonWallFunctionFvPatchScalarField` ，所以，我们假设调用 `patchI = 0` 对应的边界时，由于初始化时数据成员 `master_` 赋值为 `-1` ，所以，调用 `patchI = 0` 的边界时， `setMaster` 函数中的操作会进行。而根据上面的分析，调用 `patchI = 0` 的边界时， `setMaster` 函数同时也将 `patchI = 1` 边界的数据成员 `master_` 赋值为 `0`了，所以，在外部调用 `patchI = 1` 的边界时， `setMaster` 函数将不作任何操作，直接返回。同样的，在外部调用 `patchI = 0` 的边界时，`patch.index() == master_` 条件是成立的，所以 `createAveragingWeights()` 和 `calculateTurbulenceFields(turbulence, G(true), epsilon(true));` 两个语句将会执行；而在外部调用 `patchI = 1` 边界时，由于 `patch.index() == master_` 不成立，这两个语句将不执行。\n \n 再继续往前看， `const scalarField& G0 = this->G();    const scalarField& epsilon0 = this->epsilon();` ，这里是将成员函数 `G` 和 `epsilon` 的返回值分别赋给变量 `G0` 和 `epsilon0`。开看一下成员函数的定义\n```\nscalarField& epsilonWallFunctionFvPatchScalarField::G(bool init)\n{\n    if (patch().index() == master_) // 只有头一个被处理的边界满足这个条件\n    {\n        if (init) // init 缺省值是 false \n        {\n            G_ = 0.0;\n        }\n        return G_;\n    }\n    return epsilonPatch(master_).G(); // 对于不是 master 的边界，返回master边界的数据成员 G_\n}\n\nscalarField& epsilonWallFunctionFvPatchScalarField::epsilon(bool init)\n{\n    if (patch().index() == master_)\n    {\n        if (init)\n        {\n            epsilon_ = 0.0;\n        }\n        return epsilon_;\n    }\n    return epsilonPatch(master_).epsilon(init);\n}\n```\n类似的，对于 `patchI = 0`， `patch().index() == master_` ，所以返回值为 `patchI = 0` 边界的数据成员 `G_` 或  `epsilon_` (`init` 的缺省值是 `false`)；而对于 `patchI = 1`边界，返回的是 `patchI = master_` 对应边界的数据成员 `G_` 或  `epsilon_`，而根据上面的分析， `patchI= 1` 的边界的数据成员 `master_ = 0`，因此， `patchI = 1` 的边界的成员函数返回的是 `patchI = 0`边界的相应的数据成员。\n\n再往下的内容就很简单了，只是将得到的 `G0` 和 `epsilon0` 的值分别赋给当前边界的临近边界网格而已。\n\n到此，代码的框架就基本清晰了，小结一下就是，如果对于某个算例，有多个边界上需要用到 `epsilonWallFunctionFvPatchScalarField` 类型的边界条件，则，编号更小的那个边界将会被设置成 `master`。所有的相关计算都在调用 `master` 边界的时候进行，非 `master` 的边界，则只需要从 `master` 那里读取结果即可！ \n\n接下来看看外部调用 `master` 边界的时候，具体做了哪些计算，主要就是看 `createAveragingWeights()` 和 `calculateTurbulenceFields(turbulence, G(true), epsilon(true));` 这两条语句了。\n```\nvoid epsilonWallFunctionFvPatchScalarField::createAveragingWeights()\n{\n    const volScalarField& epsilon =\n        static_cast<const volScalarField&>(this->dimensionedInternalField());\n\n    const volScalarField::GeometricBoundaryField& bf = epsilon.boundaryField();\n\n    const fvMesh& mesh = epsilon.mesh();\n\n    if (initialised_ && !mesh.changing())\n    {\n        return;\n    }\n\n    volScalarField weights\n    (\n        IOobject\n        (\n            \"weights\",\n            mesh.time().timeName(),\n            mesh,\n            IOobject::NO_READ,\n            IOobject::NO_WRITE,\n            false // do not register\n        ),\n        mesh,\n        dimensionedScalar(\"zero\", dimless, 0.0)\n    );\n\n    DynamicList<label> epsilonPatches(bf.size());\n    //遍历所有边界，如果边界类型是 epsilonWallFunctionFvPatchScalarField 则将该边界放到 epsilonPatches 这个动态 list 中。\n    forAll(bf, patchI)\n    {\n        if (isA<epsilonWallFunctionFvPatchScalarField>(bf[patchI]))\n        {\n            epsilonPatches.append(patchI);\n\n            const labelUList& faceCells = bf[patchI].patch().faceCells();\n            forAll(faceCells, i)\n            {\n                label cellI = faceCells[i];\n            // weight 衡量的是网格cellI有多少个边界面使用了 epsilonWallFunctionFvPatchScalarField 类型的边界条件\n                 weights[cellI]++;\n            }\n        }\n    }\n    cornerWeights_.setSize(bf.size());\n    \n    // 遍历所有 epsilonWallFunctionFvPatchScalarField 类型的边界\n    forAll(epsilonPatches, i)\n    {\n        label patchI = epsilonPatches[i];\n        const fvPatchScalarField& wf = weights.boundaryField()[patchI];\n    //cornerWeights_存储的所有边界面的weight的倒数，边界面的weight等于其所属网格的weight。所以，如果有一个网格包含两个使用epsilonWallFunction的边界面，那么根据上面的计算，这个网格的weight将是 2，而这两个边界面的 cornerWeights_ 则都是 1/2。 \n        cornerWeights_[patchI] = 1.0/wf.patchInternalField();\n    }\n    // 将数据成员 G_ 和 epsilon_ 初始化为0\n    G_.setSize(dimensionedInternalField().size(), 0.0);\n    epsilon_.setSize(dimensionedInternalField().size(), 0.0);\n\n    initialised_ = true;\n}\n\nvoid epsilonWallFunctionFvPatchScalarField::calculateTurbulenceFields\n(\n    const turbulenceModel& turbulence,\n    scalarField& G0,\n    scalarField& epsilon0\n)\n{\n    // accumulate all of the G and epsilon contributions\n    //cornerWeights_ 是一个二维 list，这里是遍历这个list 的第一层\n    forAll(cornerWeights_, patchI)\n    {\n        if (!cornerWeights_[patchI].empty()) // 如果是empty，意味着这个对应的边界不是epsilonWallFunction类型，所以就不需要考虑\n        {\n            epsilonWallFunctionFvPatchScalarField& epf = epsilonPatch(patchI);\n\n            const List<scalar>& w = cornerWeights_[patchI];\n\n     // 非 empty 则调用 calculate 函数更新 G0 和 epsilon 的值\n            epf.calculate(turbulence, w, epf.patch(), G0, epsilon0);\n        }\n    }\n    // apply zero-gradient condition for epsilon\n    forAll(cornerWeights_, patchI)\n    {\n        if (!cornerWeights_[patchI].empty())\n        {\n            epsilonWallFunctionFvPatchScalarField& epf = epsilonPatch(patchI);\n\n    // 对 epsilon 使用 零梯度边界条件，即将上面计算得到的临近壁面网格的epsilon的值存储在壁面。\n            epf == scalarField(epsilon0, epf.patch().faceCells());\n        }\n    }\n}\n\nvoid epsilonWallFunctionFvPatchScalarField::calculate\n(\n    const turbulenceModel& turbulence,\n    const List<scalar>& cornerWeights,\n    const fvPatch& patch,\n    scalarField& G,\n    scalarField& epsilon\n)\n{\n    const label patchI = patch.index();\n    const scalarField& y = turbulence.y()[patchI];\n    const scalar Cmu25 = pow025(Cmu_);\n    const scalar Cmu75 = pow(Cmu_, 0.75);\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n    const tmp<volScalarField> tnut = turbulence.nut();\n    const volScalarField& nut = tnut();\n    const scalarField& nutw = nut.boundaryField()[patchI];\n    const fvPatchVectorField& Uw = turbulence.U().boundaryField()[patchI];\n    const scalarField magGradUw(mag(Uw.snGrad()));\n\n    // Set epsilon and G\n    遍历参数 patch 对应的边界的每一个面\n    forAll(nutw, faceI)\n    {\n        label cellI = patch.faceCells()[faceI];\n        scalar w = cornerWeights[faceI];\n        \n        epsilon[cellI] += w*Cmu75*pow(k[cellI], 1.5)/(kappa_*y[faceI]);\n        G[cellI] +=\n            w\n           *(nutw[faceI] + nuw[faceI])\n           *magGradUw[faceI]\n           *Cmu25*sqrt(k[cellI])\n           /(kappa_*y[faceI]);\n    }\n}\n```\n`calculate` 函数中进行的是实际的计算过程，主要是更新了临近壁面网格的 `epsilon` 和 `G` 的值，计算公式如下：\n$$\n\\varepsilon\\_c = \\frac{1}{N} \\sum\\_{f=i}^{N}\\left( \\frac{c\\_\\mu^{3/4} k\\_C^{3/2}}{\\kappa y\\_i}\\right) \\\\\\\\\n\\text{相当于} \\quad \\quad \\quad \n\\varepsilon ^+ = \\frac{1}{\\kappa y^+} \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \n$$\n\n$$\nG\\_c = \\frac{1}{N} \\sum\\_{f=i}^{N}\\left( \\frac{(\\nu + \\nu\\_t)\\cdot |\\tfrac{U\\_i-U\\_c}{d}|\\cdot c\\_\\mu^{1/4} k\\_C^{1/2}}{\\kappa y\\_i}\\right)\n$$\n这里的 `Uw.snGrad()` 是 `fvPatchFields<Type>` 类的成员函数： \n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> > Foam::fvPatchField<Type>::snGrad() const\n{\n     return patch_.deltaCoeffs()*(*this - patchInternalField());\n}\n```\n公式中下标 `c` 表示临近边界的网格， `i` 表示网格 `c` 包含的某个边界面元。`y` 和 `d` 都表示边界面元所属网格中心到该面元的垂直距离。 \n\n还有一个重要的函数， `manipulateMatrix` \n```\nvoid epsilonWallFunctionFvPatchScalarField::manipulateMatrix\n(\n    fvMatrix<scalar>& matrix\n)\n{\n    if (manipulatedMatrix())\n    {\n        return;\n    }\n\n    matrix.setValues(patch().faceCells(), patchInternalField());\n\n    fvPatchField<scalar>::manipulateMatrix(matrix);\n}\n```\n这个函数的功能是修改 matrix 中的值，将当前 patch 每一个面所属网格的值更新到 matrix 中，参考[这个帖子](http://www.cfd-online.com/Forums/openfoam-solving/132703-boundarymanipulate.html)。\n\n如果不是使用的低雷诺数湍流模型，则 $\\varepsilon$ 应该使用这个边界条件。理论上，边界第一层网格应该设置在对数区。什么是低雷诺数湍流模型呢？[这篇帖子](http://www.cfd-online.com/Forums/openfoam/125473-low-reynolds-turbulence-models.html)的三楼有精彩的解释。\n\n+ (2). epsilonLowReWallFunction\n\n`epsilonLowReWallFunction` 继承自 `epsilonWallFunction` ，在此基础上，增加了一个成员函数 `yPlusLam`，并重新定义了 `calculate` 函数\n```\nscalar epsilonLowReWallFunctionFvPatchScalarField::yPlusLam\n(\n    const scalar kappa,\n    const scalar E\n)\n{\n    scalar ypl = 11.0;\n    for (int i=0; i<10; i++)\n    {\n        ypl = log(max(E*ypl, 1))/kappa;\n    }\n    return ypl;\n}\n```\n这个跟 `kLowReWallFunction` 里是一样的，不再赘述。\n```\nvoid epsilonLowReWallFunctionFvPatchScalarField::calculate\n(\n    const turbulenceModel& turbulence,\n    const List<scalar>& cornerWeights,\n    const fvPatch& patch,\n    scalarField& G,\n    scalarField& epsilon\n)\n{\n    const label patchI = patch.index();\n    const scalarField& y = turbulence.y()[patchI];\n    const scalar Cmu25 = pow025(Cmu_);\n    const scalar Cmu75 = pow(Cmu_, 0.75);\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n    const tmp<volScalarField> tnut = turbulence.nut();\n    const volScalarField& nut = tnut();\n    const scalarField& nutw = nut.boundaryField()[patchI];\n    const fvPatchVectorField& Uw = turbulence.U().boundaryField()[patchI];\n    const scalarField magGradUw(mag(Uw.snGrad()));\n    \n// Set epsilon and G\n    forAll(nutw, faceI)\n    {\n        label cellI = patch.faceCells()[faceI];\n\n        scalar yPlus = Cmu25*sqrt(k[cellI])*y[faceI]/nuw[faceI];\n\n        scalar w = cornerWeights[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            epsilon[cellI] = w*Cmu75*pow(k[cellI], 1.5)/(kappa_*y[faceI]);\n        }\n        else\n        {\n            epsilon[cellI] = w*2.0*k[cellI]*nuw[faceI]/sqr(y[faceI]);\n        }\n        G[cellI] =\n            w\n           *(nutw[faceI] + nuw[faceI])\n           *magGradUw[faceI]\n           *Cmu25*sqrt(k[cellI])\n           /(kappa_*y[faceI]);\n    }\n}\n```\n这里需要根据 `yPlus` 和 `yPlusLam_` 的相对大小来选择不同的计算方式。只是，上面这段来自 OpenFOAM-2.3.1 的代码是有问题的！在OpenFOAM-3.0.1 中已经修复成如下\n```\n forAll(nutw, facei)\n    {\n        label celli = patch.faceCells()[facei];\n\n        scalar yPlus = Cmu25*sqrt(k[celli])*y[facei]/nuw[facei];\n\n        scalar w = cornerWeights[facei];\n\n        if (yPlus > yPlusLam_)\n        {\n            epsilon0[celli] += w*Cmu75*pow(k[celli], 1.5)/(kappa_*y[facei]);\n\n            G0[celli] +=\n                w\n               *(nutw[facei] + nuw[facei])\n               *magGradUw[facei]\n               *Cmu25*sqrt(k[celli])\n               /(kappa_*y[facei]);\n        }\n        else\n        {\n            epsilon0[celli] += w*2.0*k[celli]*nuw[facei]/sqr(y[facei]);\n            G0[celli] += G[celli];\n        }\n    }\n}\n```\n`yPlus > yPlusLam_` 时，与 `epsilonWallFunction` 是一样的； \n`yPlus < yPlusLam_` 时\n$$\n\\varepsilon\\_c = \\frac{1}{N} \\sum\\_{f=i}^{N}\\left( \\frac{2\\cdot k\\_C \\nu\\_i}{y^2\\_i}\\right)\n$$\n这个公式等价于\n$$\n\\varepsilon ^+ = 2\\frac{k^+}{(y^+)^2}\n$$\n\n`G` 则取在湍流模型中定义的值，不作修改。 不过，这里 `G0[celli] += G[celli]` 意味着假设有一个网格有两个边界面，则这个网格的中计算得到的 `G0` ，将是在湍流模型中定义的该网格中的 G 值的 2 倍，即认为每一个边界面对都该网格内的湍动能生成有贡献。\n\n这个边界是给低雷诺数的 $k-\\varepsilon$ 模型以及 $v^2\\text{-}f$ 模型使用的。用 OpenFOAM-3.0 以下版本的注意了，这些版本的 `epsilonLowReWallFunction` 有问题，**一定不要忘了修正一下上面提到的那个bug **！\n\n\n##### 4. $\\omega$ 的壁面函数\nOpenFOAM 中只提供了一个 `omegaWallFunction`，这个壁面函数，属于一种自动壁面函数，能自动地根据 $y^+$ 的值来在粘性层和对数层切换，过渡层则采用粘性层和对数层混合的结果。\n`omegaWallFunction` 与 `epsilonWallFunction` 类似，也是需要计算 $\\omega$ 和 $P_k$ 在临近边界网格里的值，因此也需要考虑一个网格包含两个以上边界面的情况。具体处理方法跟 `epsilonWallFunction` 是一样的 ，所以这里就不重复了，只看具体的计算 $\\omega$ 和 $P_k$ 的公式\n```\nvoid omegaWallFunctionFvPatchScalarField::calculate\n(\n    const turbulenceModel& turbulence,\n    const List<scalar>& cornerWeights,\n    const fvPatch& patch,\n    scalarField& G,\n    scalarField& omega\n)\n{\n    const label patchI = patch.index();\n    const scalarField& y = turbulence.y()[patchI];\n    const scalar Cmu25 = pow025(Cmu_);\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n    const tmp<volScalarField> tnut = turbulence.nut();\n    const volScalarField& nut = tnut();\n    const scalarField& nutw = nut.boundaryField()[patchI];\n    const fvPatchVectorField& Uw = turbulence.U().boundaryField()[patchI];\n    const scalarField magGradUw(mag(Uw.snGrad()));\n\n    // Set omega and G\n    forAll(nutw, faceI)\n    {\n        label cellI = patch.faceCells()[faceI];\n        scalar w = cornerWeights[faceI];\n        scalar omegaVis = 6.0*nuw[faceI]/(beta1_*sqr(y[faceI]));\n        scalar omegaLog = sqrt(k[cellI])/(Cmu25*kappa_*y[faceI]);\n        omega[cellI] += w*sqrt(sqr(omegaVis) + sqr(omegaLog));\n        G[cellI] +=\n            w\n           *(nutw[faceI] + nuw[faceI])\n           *magGradUw[faceI]\n           *Cmu25*sqrt(k[cellI])\n           /(kappa_*y[faceI]);\n    }\n}\n```\n这里， `omegaVis` 和 `omegaLog` 分别指的是在假定第一层网格位于粘性底层和对数层时得到的 `omega` 的解析解\n$$\n\\omega\\_{Vis} = \\frac{6.0\\nu}{\\beta\\_1y^2} \\\\\n\\omega\\_{Log} = \\frac{k\\_C^{1/2}}{C\\_\\mu^{1/4}\\kappa y}\n$$\n然后，将 $\\omega\\_{Vis}$ 和 $\\omega\\_{Log}$ 用一个函数混合起来，就得到了\n$$\n\\omega = \\sqrt{\\omega\\_{Vis}^2 + \\omega\\_{Log}^2}\n$$\n只是，这里的湍动能生成项，却似乎并没有使用混合的方法，而是用的基于对数律的公式：\n$$\nG = \\frac{(\\nu + \\nu\\_t)\\cdot |\\frac{U\\_c-U\\_w}{d}|\\cdot C\\_\\mu^{1/4}k\\_C^{1/2}}{\\kappa y}\n$$\n\n$omega$ 方程是能直接积分到壁面，所以，如果使用基于 $\\omega$ 的湍流模型，$\\omega$ 变量直接使用这个边界条件就可以了。\n","source":"_posts/wallFunctions3.md","raw":"title: \"OpenFOAM 中的壁面函数（三）\"\ndate: 2016-04-25 00:43:34\ntags:\n- wall functions\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n这篇来看看计算湍动能 $\\varepsilon$  和 $\\omega$ 的壁面函数。\n\n<!--more-->\n\n##### 3. 湍动能耗散 $\\varepsilon$ 的壁面函数\n本篇来看看 OpenFOAM 中的 `epsilonWallFunction`，共有两个： `epsilonWallFunction` 和 `epsilonLowReWallFunction`。\n+ (1). epsilonWallFunction\n\n`epsilonWallFunction` 代码比前面的 `kqRWallFunction` 复杂多了，主要原因在于这里需要得到的是 `epsilon` 在临近网格的值，而且，需要考虑包含两个边界面的网格。这里先来梳理代码的脉络，然后再看具体的计算细节。\n外部调用的主要是 `updateCoeffs()` 函数，所以，从这个函数看起。\n```cpp\nvoid epsilonWallFunctionFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n    const turbulenceModel& turbulence =\n        db().lookupObject<turbulenceModel>(turbulenceModel::typeName);\n\n    setMaster();\n\n    if (patch().index() == master_)\n    {\n        createAveragingWeights();\n        calculateTurbulenceFields(turbulence, G(true), epsilon(true));\n    }\n    const scalarField& G0 = this->G();\n    const scalarField& epsilon0 = this->epsilon();\n\n    typedef DimensionedField<scalar, volMesh> FieldType;\n\n    FieldType& G =\n        const_cast<FieldType&>\n        (\n            db().lookupObject<FieldType>(turbulence.GName())\n        );\n    //这里是获取内部场，所以，修改这里的引用 \"epsilon\",相当于修改 epsilon 的内部场值。\n    FieldType& epsilon = const_cast<FieldType&>(dimensionedInternalField());\n\n    forAll(*this, faceI)\n    {\n        label cellI = patch().faceCells()[faceI];\n\n        G[cellI] = G0[cellI];\n        epsilon[cellI] = epsilon0[cellI];\n    }\n    fvPatchField<scalar>::updateCoeffs();\n}\n```\n 一步一步来看。首先是调用了 `setMaster()` 函数，来看看这个函数以及相关的一个函数 `epsilonPatch` 的代码： \n```\nvoid epsilonWallFunctionFvPatchScalarField::setMaster()\n{\n    if (master_ != -1) // 如果当前处理的边界的 master_ != -1，说明它已被处理过，直接返回\n    {\n        return;\n    }\n    const volScalarField& epsilon =\n        static_cast<const volScalarField&>(this->dimensionedInternalField());\n\n    const volScalarField::GeometricBoundaryField& bf = epsilon.boundaryField();\n\n    label master = -1;\n    forAll(bf, patchI)\n    {\n        if (isA<epsilonWallFunctionFvPatchScalarField>(bf[patchI]))\n        {\n            epsilonWallFunctionFvPatchScalarField& epf = epsilonPatch(patchI);\n\n            if (master == -1) // 只有头一个被处理的边界满足这个条件\n            {\n                master = patchI;\n            }\n\n            epf.master() = master; // 这意味着所有边界的 master_ 数据成员都将赋值为头一个被处理的边界的编号，即第一个被处理的边界是master\n        }\n    }\n}\n\nepsilonWallFunctionFvPatchScalarField&\nepsilonWallFunctionFvPatchScalarField::epsilonPatch(const label patchI)\n{\n    const volScalarField& epsilon =\n        static_cast<const volScalarField&>(this->dimensionedInternalField());\n\n    const volScalarField::GeometricBoundaryField& bf = epsilon.boundaryField();\n\n    const epsilonWallFunctionFvPatchScalarField& epf =\n        refCast<const epsilonWallFunctionFvPatchScalarField>(bf[patchI]);\n\n    return const_cast<epsilonWallFunctionFvPatchScalarField&>(epf);\n}\n\n```\n 从上述代码可以看出， `epsilonPatch` 函数需要一个参数，这个参数的含义是某一个边界的序号，返回的是指向这个边界的一个 `epsilonWallFunctionFvPatchScalarField` 类型的引用。\n在此基础上，再来看 `setMaster`。先判断当前边界的数据成员 `master_` 是否不等于-1，如果成立则不做任何操作，直接返回；否则，先获取到 `epsilon` 的所有边界，存在变量 `bf` 中，然后，遍历 `bf` ，如果边界的类型是 `epsilonWallFunctionFvPatchScalarField`，则判断临时变量 `master` 是否等于 `-1`，等于则将边界的序号 `patchI`  赋值给 `master`，并临时变量 `master` 的值赋给 `patchI` 对应边界的数据成员 `master_`。 举个例子，假设有一个算例，有两个边界上使用了 `epsilonWallFunctionFvPatchScalarField` 类型的边界条件，两个边界的编号分别是 `patchI = 0` 和 `patchI = 1`。则在上述循环过程中，当 `patchI = 0`时， `master == -1` 肯定成立。于是，`patchI = 0` 对应边界的数据成员 `master_` 被赋值为0；而当遍历到 `patchI = 1` 时， 此时`master = 0`，所以，结果是 `patchI = 1` 的边界的数据成员 `master_` 也被赋值为0。 \n\n 继续向下看，如果 `patch.index() == master_` ，则调用两个函数。这个怎么理解呢？还以上面的那个简单例子来说明。注意，在外部调用边界条件的时候，也是会依次调用一个场的所有边界的边界条件的。在这里的简单例子中，有两个边界的类型是 `epsilonWallFunctionFvPatchScalarField` ，所以，我们假设调用 `patchI = 0` 对应的边界时，由于初始化时数据成员 `master_` 赋值为 `-1` ，所以，调用 `patchI = 0` 的边界时， `setMaster` 函数中的操作会进行。而根据上面的分析，调用 `patchI = 0` 的边界时， `setMaster` 函数同时也将 `patchI = 1` 边界的数据成员 `master_` 赋值为 `0`了，所以，在外部调用 `patchI = 1` 的边界时， `setMaster` 函数将不作任何操作，直接返回。同样的，在外部调用 `patchI = 0` 的边界时，`patch.index() == master_` 条件是成立的，所以 `createAveragingWeights()` 和 `calculateTurbulenceFields(turbulence, G(true), epsilon(true));` 两个语句将会执行；而在外部调用 `patchI = 1` 边界时，由于 `patch.index() == master_` 不成立，这两个语句将不执行。\n \n 再继续往前看， `const scalarField& G0 = this->G();    const scalarField& epsilon0 = this->epsilon();` ，这里是将成员函数 `G` 和 `epsilon` 的返回值分别赋给变量 `G0` 和 `epsilon0`。开看一下成员函数的定义\n```\nscalarField& epsilonWallFunctionFvPatchScalarField::G(bool init)\n{\n    if (patch().index() == master_) // 只有头一个被处理的边界满足这个条件\n    {\n        if (init) // init 缺省值是 false \n        {\n            G_ = 0.0;\n        }\n        return G_;\n    }\n    return epsilonPatch(master_).G(); // 对于不是 master 的边界，返回master边界的数据成员 G_\n}\n\nscalarField& epsilonWallFunctionFvPatchScalarField::epsilon(bool init)\n{\n    if (patch().index() == master_)\n    {\n        if (init)\n        {\n            epsilon_ = 0.0;\n        }\n        return epsilon_;\n    }\n    return epsilonPatch(master_).epsilon(init);\n}\n```\n类似的，对于 `patchI = 0`， `patch().index() == master_` ，所以返回值为 `patchI = 0` 边界的数据成员 `G_` 或  `epsilon_` (`init` 的缺省值是 `false`)；而对于 `patchI = 1`边界，返回的是 `patchI = master_` 对应边界的数据成员 `G_` 或  `epsilon_`，而根据上面的分析， `patchI= 1` 的边界的数据成员 `master_ = 0`，因此， `patchI = 1` 的边界的成员函数返回的是 `patchI = 0`边界的相应的数据成员。\n\n再往下的内容就很简单了，只是将得到的 `G0` 和 `epsilon0` 的值分别赋给当前边界的临近边界网格而已。\n\n到此，代码的框架就基本清晰了，小结一下就是，如果对于某个算例，有多个边界上需要用到 `epsilonWallFunctionFvPatchScalarField` 类型的边界条件，则，编号更小的那个边界将会被设置成 `master`。所有的相关计算都在调用 `master` 边界的时候进行，非 `master` 的边界，则只需要从 `master` 那里读取结果即可！ \n\n接下来看看外部调用 `master` 边界的时候，具体做了哪些计算，主要就是看 `createAveragingWeights()` 和 `calculateTurbulenceFields(turbulence, G(true), epsilon(true));` 这两条语句了。\n```\nvoid epsilonWallFunctionFvPatchScalarField::createAveragingWeights()\n{\n    const volScalarField& epsilon =\n        static_cast<const volScalarField&>(this->dimensionedInternalField());\n\n    const volScalarField::GeometricBoundaryField& bf = epsilon.boundaryField();\n\n    const fvMesh& mesh = epsilon.mesh();\n\n    if (initialised_ && !mesh.changing())\n    {\n        return;\n    }\n\n    volScalarField weights\n    (\n        IOobject\n        (\n            \"weights\",\n            mesh.time().timeName(),\n            mesh,\n            IOobject::NO_READ,\n            IOobject::NO_WRITE,\n            false // do not register\n        ),\n        mesh,\n        dimensionedScalar(\"zero\", dimless, 0.0)\n    );\n\n    DynamicList<label> epsilonPatches(bf.size());\n    //遍历所有边界，如果边界类型是 epsilonWallFunctionFvPatchScalarField 则将该边界放到 epsilonPatches 这个动态 list 中。\n    forAll(bf, patchI)\n    {\n        if (isA<epsilonWallFunctionFvPatchScalarField>(bf[patchI]))\n        {\n            epsilonPatches.append(patchI);\n\n            const labelUList& faceCells = bf[patchI].patch().faceCells();\n            forAll(faceCells, i)\n            {\n                label cellI = faceCells[i];\n            // weight 衡量的是网格cellI有多少个边界面使用了 epsilonWallFunctionFvPatchScalarField 类型的边界条件\n                 weights[cellI]++;\n            }\n        }\n    }\n    cornerWeights_.setSize(bf.size());\n    \n    // 遍历所有 epsilonWallFunctionFvPatchScalarField 类型的边界\n    forAll(epsilonPatches, i)\n    {\n        label patchI = epsilonPatches[i];\n        const fvPatchScalarField& wf = weights.boundaryField()[patchI];\n    //cornerWeights_存储的所有边界面的weight的倒数，边界面的weight等于其所属网格的weight。所以，如果有一个网格包含两个使用epsilonWallFunction的边界面，那么根据上面的计算，这个网格的weight将是 2，而这两个边界面的 cornerWeights_ 则都是 1/2。 \n        cornerWeights_[patchI] = 1.0/wf.patchInternalField();\n    }\n    // 将数据成员 G_ 和 epsilon_ 初始化为0\n    G_.setSize(dimensionedInternalField().size(), 0.0);\n    epsilon_.setSize(dimensionedInternalField().size(), 0.0);\n\n    initialised_ = true;\n}\n\nvoid epsilonWallFunctionFvPatchScalarField::calculateTurbulenceFields\n(\n    const turbulenceModel& turbulence,\n    scalarField& G0,\n    scalarField& epsilon0\n)\n{\n    // accumulate all of the G and epsilon contributions\n    //cornerWeights_ 是一个二维 list，这里是遍历这个list 的第一层\n    forAll(cornerWeights_, patchI)\n    {\n        if (!cornerWeights_[patchI].empty()) // 如果是empty，意味着这个对应的边界不是epsilonWallFunction类型，所以就不需要考虑\n        {\n            epsilonWallFunctionFvPatchScalarField& epf = epsilonPatch(patchI);\n\n            const List<scalar>& w = cornerWeights_[patchI];\n\n     // 非 empty 则调用 calculate 函数更新 G0 和 epsilon 的值\n            epf.calculate(turbulence, w, epf.patch(), G0, epsilon0);\n        }\n    }\n    // apply zero-gradient condition for epsilon\n    forAll(cornerWeights_, patchI)\n    {\n        if (!cornerWeights_[patchI].empty())\n        {\n            epsilonWallFunctionFvPatchScalarField& epf = epsilonPatch(patchI);\n\n    // 对 epsilon 使用 零梯度边界条件，即将上面计算得到的临近壁面网格的epsilon的值存储在壁面。\n            epf == scalarField(epsilon0, epf.patch().faceCells());\n        }\n    }\n}\n\nvoid epsilonWallFunctionFvPatchScalarField::calculate\n(\n    const turbulenceModel& turbulence,\n    const List<scalar>& cornerWeights,\n    const fvPatch& patch,\n    scalarField& G,\n    scalarField& epsilon\n)\n{\n    const label patchI = patch.index();\n    const scalarField& y = turbulence.y()[patchI];\n    const scalar Cmu25 = pow025(Cmu_);\n    const scalar Cmu75 = pow(Cmu_, 0.75);\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n    const tmp<volScalarField> tnut = turbulence.nut();\n    const volScalarField& nut = tnut();\n    const scalarField& nutw = nut.boundaryField()[patchI];\n    const fvPatchVectorField& Uw = turbulence.U().boundaryField()[patchI];\n    const scalarField magGradUw(mag(Uw.snGrad()));\n\n    // Set epsilon and G\n    遍历参数 patch 对应的边界的每一个面\n    forAll(nutw, faceI)\n    {\n        label cellI = patch.faceCells()[faceI];\n        scalar w = cornerWeights[faceI];\n        \n        epsilon[cellI] += w*Cmu75*pow(k[cellI], 1.5)/(kappa_*y[faceI]);\n        G[cellI] +=\n            w\n           *(nutw[faceI] + nuw[faceI])\n           *magGradUw[faceI]\n           *Cmu25*sqrt(k[cellI])\n           /(kappa_*y[faceI]);\n    }\n}\n```\n`calculate` 函数中进行的是实际的计算过程，主要是更新了临近壁面网格的 `epsilon` 和 `G` 的值，计算公式如下：\n$$\n\\varepsilon\\_c = \\frac{1}{N} \\sum\\_{f=i}^{N}\\left( \\frac{c\\_\\mu^{3/4} k\\_C^{3/2}}{\\kappa y\\_i}\\right) \\\\\\\\\n\\text{相当于} \\quad \\quad \\quad \n\\varepsilon ^+ = \\frac{1}{\\kappa y^+} \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \\quad \n$$\n\n$$\nG\\_c = \\frac{1}{N} \\sum\\_{f=i}^{N}\\left( \\frac{(\\nu + \\nu\\_t)\\cdot |\\tfrac{U\\_i-U\\_c}{d}|\\cdot c\\_\\mu^{1/4} k\\_C^{1/2}}{\\kappa y\\_i}\\right)\n$$\n这里的 `Uw.snGrad()` 是 `fvPatchFields<Type>` 类的成员函数： \n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> > Foam::fvPatchField<Type>::snGrad() const\n{\n     return patch_.deltaCoeffs()*(*this - patchInternalField());\n}\n```\n公式中下标 `c` 表示临近边界的网格， `i` 表示网格 `c` 包含的某个边界面元。`y` 和 `d` 都表示边界面元所属网格中心到该面元的垂直距离。 \n\n还有一个重要的函数， `manipulateMatrix` \n```\nvoid epsilonWallFunctionFvPatchScalarField::manipulateMatrix\n(\n    fvMatrix<scalar>& matrix\n)\n{\n    if (manipulatedMatrix())\n    {\n        return;\n    }\n\n    matrix.setValues(patch().faceCells(), patchInternalField());\n\n    fvPatchField<scalar>::manipulateMatrix(matrix);\n}\n```\n这个函数的功能是修改 matrix 中的值，将当前 patch 每一个面所属网格的值更新到 matrix 中，参考[这个帖子](http://www.cfd-online.com/Forums/openfoam-solving/132703-boundarymanipulate.html)。\n\n如果不是使用的低雷诺数湍流模型，则 $\\varepsilon$ 应该使用这个边界条件。理论上，边界第一层网格应该设置在对数区。什么是低雷诺数湍流模型呢？[这篇帖子](http://www.cfd-online.com/Forums/openfoam/125473-low-reynolds-turbulence-models.html)的三楼有精彩的解释。\n\n+ (2). epsilonLowReWallFunction\n\n`epsilonLowReWallFunction` 继承自 `epsilonWallFunction` ，在此基础上，增加了一个成员函数 `yPlusLam`，并重新定义了 `calculate` 函数\n```\nscalar epsilonLowReWallFunctionFvPatchScalarField::yPlusLam\n(\n    const scalar kappa,\n    const scalar E\n)\n{\n    scalar ypl = 11.0;\n    for (int i=0; i<10; i++)\n    {\n        ypl = log(max(E*ypl, 1))/kappa;\n    }\n    return ypl;\n}\n```\n这个跟 `kLowReWallFunction` 里是一样的，不再赘述。\n```\nvoid epsilonLowReWallFunctionFvPatchScalarField::calculate\n(\n    const turbulenceModel& turbulence,\n    const List<scalar>& cornerWeights,\n    const fvPatch& patch,\n    scalarField& G,\n    scalarField& epsilon\n)\n{\n    const label patchI = patch.index();\n    const scalarField& y = turbulence.y()[patchI];\n    const scalar Cmu25 = pow025(Cmu_);\n    const scalar Cmu75 = pow(Cmu_, 0.75);\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n    const tmp<volScalarField> tnut = turbulence.nut();\n    const volScalarField& nut = tnut();\n    const scalarField& nutw = nut.boundaryField()[patchI];\n    const fvPatchVectorField& Uw = turbulence.U().boundaryField()[patchI];\n    const scalarField magGradUw(mag(Uw.snGrad()));\n    \n// Set epsilon and G\n    forAll(nutw, faceI)\n    {\n        label cellI = patch.faceCells()[faceI];\n\n        scalar yPlus = Cmu25*sqrt(k[cellI])*y[faceI]/nuw[faceI];\n\n        scalar w = cornerWeights[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            epsilon[cellI] = w*Cmu75*pow(k[cellI], 1.5)/(kappa_*y[faceI]);\n        }\n        else\n        {\n            epsilon[cellI] = w*2.0*k[cellI]*nuw[faceI]/sqr(y[faceI]);\n        }\n        G[cellI] =\n            w\n           *(nutw[faceI] + nuw[faceI])\n           *magGradUw[faceI]\n           *Cmu25*sqrt(k[cellI])\n           /(kappa_*y[faceI]);\n    }\n}\n```\n这里需要根据 `yPlus` 和 `yPlusLam_` 的相对大小来选择不同的计算方式。只是，上面这段来自 OpenFOAM-2.3.1 的代码是有问题的！在OpenFOAM-3.0.1 中已经修复成如下\n```\n forAll(nutw, facei)\n    {\n        label celli = patch.faceCells()[facei];\n\n        scalar yPlus = Cmu25*sqrt(k[celli])*y[facei]/nuw[facei];\n\n        scalar w = cornerWeights[facei];\n\n        if (yPlus > yPlusLam_)\n        {\n            epsilon0[celli] += w*Cmu75*pow(k[celli], 1.5)/(kappa_*y[facei]);\n\n            G0[celli] +=\n                w\n               *(nutw[facei] + nuw[facei])\n               *magGradUw[facei]\n               *Cmu25*sqrt(k[celli])\n               /(kappa_*y[facei]);\n        }\n        else\n        {\n            epsilon0[celli] += w*2.0*k[celli]*nuw[facei]/sqr(y[facei]);\n            G0[celli] += G[celli];\n        }\n    }\n}\n```\n`yPlus > yPlusLam_` 时，与 `epsilonWallFunction` 是一样的； \n`yPlus < yPlusLam_` 时\n$$\n\\varepsilon\\_c = \\frac{1}{N} \\sum\\_{f=i}^{N}\\left( \\frac{2\\cdot k\\_C \\nu\\_i}{y^2\\_i}\\right)\n$$\n这个公式等价于\n$$\n\\varepsilon ^+ = 2\\frac{k^+}{(y^+)^2}\n$$\n\n`G` 则取在湍流模型中定义的值，不作修改。 不过，这里 `G0[celli] += G[celli]` 意味着假设有一个网格有两个边界面，则这个网格的中计算得到的 `G0` ，将是在湍流模型中定义的该网格中的 G 值的 2 倍，即认为每一个边界面对都该网格内的湍动能生成有贡献。\n\n这个边界是给低雷诺数的 $k-\\varepsilon$ 模型以及 $v^2\\text{-}f$ 模型使用的。用 OpenFOAM-3.0 以下版本的注意了，这些版本的 `epsilonLowReWallFunction` 有问题，**一定不要忘了修正一下上面提到的那个bug **！\n\n\n##### 4. $\\omega$ 的壁面函数\nOpenFOAM 中只提供了一个 `omegaWallFunction`，这个壁面函数，属于一种自动壁面函数，能自动地根据 $y^+$ 的值来在粘性层和对数层切换，过渡层则采用粘性层和对数层混合的结果。\n`omegaWallFunction` 与 `epsilonWallFunction` 类似，也是需要计算 $\\omega$ 和 $P_k$ 在临近边界网格里的值，因此也需要考虑一个网格包含两个以上边界面的情况。具体处理方法跟 `epsilonWallFunction` 是一样的 ，所以这里就不重复了，只看具体的计算 $\\omega$ 和 $P_k$ 的公式\n```\nvoid omegaWallFunctionFvPatchScalarField::calculate\n(\n    const turbulenceModel& turbulence,\n    const List<scalar>& cornerWeights,\n    const fvPatch& patch,\n    scalarField& G,\n    scalarField& omega\n)\n{\n    const label patchI = patch.index();\n    const scalarField& y = turbulence.y()[patchI];\n    const scalar Cmu25 = pow025(Cmu_);\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n    const tmp<volScalarField> tnut = turbulence.nut();\n    const volScalarField& nut = tnut();\n    const scalarField& nutw = nut.boundaryField()[patchI];\n    const fvPatchVectorField& Uw = turbulence.U().boundaryField()[patchI];\n    const scalarField magGradUw(mag(Uw.snGrad()));\n\n    // Set omega and G\n    forAll(nutw, faceI)\n    {\n        label cellI = patch.faceCells()[faceI];\n        scalar w = cornerWeights[faceI];\n        scalar omegaVis = 6.0*nuw[faceI]/(beta1_*sqr(y[faceI]));\n        scalar omegaLog = sqrt(k[cellI])/(Cmu25*kappa_*y[faceI]);\n        omega[cellI] += w*sqrt(sqr(omegaVis) + sqr(omegaLog));\n        G[cellI] +=\n            w\n           *(nutw[faceI] + nuw[faceI])\n           *magGradUw[faceI]\n           *Cmu25*sqrt(k[cellI])\n           /(kappa_*y[faceI]);\n    }\n}\n```\n这里， `omegaVis` 和 `omegaLog` 分别指的是在假定第一层网格位于粘性底层和对数层时得到的 `omega` 的解析解\n$$\n\\omega\\_{Vis} = \\frac{6.0\\nu}{\\beta\\_1y^2} \\\\\n\\omega\\_{Log} = \\frac{k\\_C^{1/2}}{C\\_\\mu^{1/4}\\kappa y}\n$$\n然后，将 $\\omega\\_{Vis}$ 和 $\\omega\\_{Log}$ 用一个函数混合起来，就得到了\n$$\n\\omega = \\sqrt{\\omega\\_{Vis}^2 + \\omega\\_{Log}^2}\n$$\n只是，这里的湍动能生成项，却似乎并没有使用混合的方法，而是用的基于对数律的公式：\n$$\nG = \\frac{(\\nu + \\nu\\_t)\\cdot |\\frac{U\\_c-U\\_w}{d}|\\cdot C\\_\\mu^{1/4}k\\_C^{1/2}}{\\kappa y}\n$$\n\n$omega$ 方程是能直接积分到壁面，所以，如果使用基于 $\\omega$ 的湍流模型，$\\omega$ 变量直接使用这个边界条件就可以了。\n","slug":"wallFunctions3","published":1,"updated":"2016-04-25T03:05:53.080Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cioiqegab000cz8mb775ky3ai"},{"title":"OpenFOAM 中的壁面函数（二）","date":"2016-04-24T16:43:29.000Z","comments":1,"_content":"\n这篇来看看计算湍动能 $k$ 的壁面函数。\n\n<!--more-->\n\n##### 2. 湍流动能 $k$ 的壁面函数\nOpenFOAM 中提供了两种 $k$ 的壁面函数， `kqRWallFunction` 和 `kLowReWallFunction` 。\n+ `kqRWallFunction` \n其实就是 `zeroGradient` ，无需多言。除非使用 $v^2\\text{-}f$ 模型，一般情况下 $k$ 应该使用这个边界条件。\n\n+ `kLowReWallFunction`\n这个壁面函数应该是可以用于低雷诺数模型的。该壁面函数继承自 `fixedValue` ：\n```\nclass kLowReWallFunctionFvPatchScalarField\n:\n    public fixedValueFvPatchField<scalar>\n{\nprotected:\n       //- Cmu coefficient\n        scalar Cmu_;\n\n        //- Von Karman constant\n        scalar kappa_;\n\n        //- E coefficient\n        scalar E_;\n\n        //- Ceps2 coefficient\n        scalar Ceps2_;\n\n        //- Y+ at the edge of the laminar sublayer\n        scalar yPlusLam_;\n        ......\n        \nkLowReWallFunctionFvPatchScalarField::kLowReWallFunctionFvPatchScalarField\n(\n    const fvPatch& p,\n    const DimensionedField<scalar, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fixedValueFvPatchField<scalar>(p, iF, dict),\n    Cmu_(dict.lookupOrDefault<scalar>(\"Cmu\", 0.09)),\n    kappa_(dict.lookupOrDefault<scalar>(\"kappa\", 0.41)),\n    E_(dict.lookupOrDefault<scalar>(\"E\", 9.8)),\n    Ceps2_(dict.lookupOrDefault<scalar>(\"Ceps2\", 1.9)),\n    yPlusLam_(yPlusLam(kappa_, E_))\n    {\n        checkType();\n    }\n\n}\n```\n核心的函数是以下两个：\n```cpp\nscalar kLowReWallFunctionFvPatchScalarField::yPlusLam\n(\n    const scalar kappa,\n    const scalar E\n)\n{\n    scalar ypl = 11.0;\n\n    for (int i=0; i<10; i++)\n    {\n        ypl = log(max(E*ypl, 1))/kappa;\n    }\n\n    return ypl;\n}\nvoid kLowReWallFunctionFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    const label patchI = patch().index();\n\n    const turbulenceModel& turbulence =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbulence.y()[patchI];\n\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n\n    const scalar Cmu25 = pow025(Cmu_);\n\n    scalarField& kw = *this; // 更新 kw 相当于更新壁面上的 k 值。\n\n    // Set k wall values\n    forAll(kw, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar uTau = Cmu25*sqrt(k[faceCellI]);\n\n        scalar yPlus = uTau*y[faceI]/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            scalar Ck = -0.416;\n            scalar Bk = 8.366;\n            kw[faceI] = Ck/kappa_*log(yPlus) + Bk;\n        }\n        else\n        {\n            scalar C = 11.0;\n            scalar Cf = (1.0/sqr(yPlus + C) + 2.0*yPlus/pow3(C) - 1.0/sqr(C));\n            kw[faceI] = 2400.0/sqr(Ceps2_)*Cf;\n        }\n\n        kw[faceI] *= sqr(uTau);\n    }\n\n    fixedValueFvPatchField<scalar>::updateCoeffs();\n\n    // TODO: perform averaging for cells sharing more than one boundary face\n}\n```\n先在函数里计算 `ypl` 的值， `updateCoeffs` 函数里根据 `yPlus` 与这个 `ypl` 的值来相对大小而采取不同的方法来计算壁面上的 $k\\_w$。 `ypl` 的计算是一个迭代过程\n$$\nypl = \\frac{\\log(\\max(E*ypl,1.0))}{\\kappa}\n$$\n初始值为 `ypl = 11.0`，迭代10次，最终结果应该是 `ypl = 11.5301073043272`。\n$y^+$ 定义为：\n$$\nu\\_\\tau = C\\_\\mu^{1/4 }\\sqrt{k\\_c} \\\\\ny^+ = \\frac{u\\_\\tau \\cdot y}{\\nu\\_w}\n$$\n壁面上的k计算方法如下：如果 $y^+ > ypl$，则\n$$\nk^+ \\_w = \\frac{C\\_k}{\\kappa}\\ln(y^+) + B\\_k\n$$\n否则\n$$\nk^+ \\_w = \\frac{2400}{C\\_{eps2}^2}\\cdot \\left[ \\frac{1}{(y^+ + C)^2} + \\frac{2y^+}{C^3} - \\frac{1}{C^2}\\right ]\n$$\n最终，壁面上的值为 $k\\_w=k^+ \\_w u\\_\\tau ^2 =k^+ \\_w C\\_\\mu^{1/2}k\\_c$ 。\n以上公式中，下标 $c$ 表示壁面单元所述网格的值，下标 $w$ 表示当前壁面上的值。\n这个壁面函数参考文献 \"Kalitzin, G., Medic, G., Iaccarino, G., Durbin, P., 2005. Near-wall behavior of RANS turbulence models and implications for wall functions. J. Comput. Phys. 204, 265–291. doi:10.1016/j.jcp.2004.10.018\"，是为 $v^2\\text{-}f$ 模型设计的。 \n\n##### $v^2$ 和 $f$ 的壁面函数\n上面提到了 $v^2\\text{-}f$ 模型，所以这里顺便来看看$v^2$ 和 $f$ 的壁面函数。这里参考的也是上面提到的那篇参考文献。\n\n+ $v^2$ 的壁函数\n```\nforAll(v2, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar uTau = Cmu25*sqrt(k[faceCellI]);\n\n        scalar yPlus = uTau*y[faceI]/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            scalar Cv2 = 0.193;\n            scalar Bv2 = -0.94;\n            v2[faceI] = Cv2/kappa_*log(yPlus) + Bv2;\n        }\n        else\n        {\n            scalar Cv2 = 0.193;\n            v2[faceI] = Cv2*pow4(yPlus);\n        }\n\n        v2[faceI] *= sqr(uTau);\n    }\n\n    fixedValueFvPatchField<scalar>::updateCoeffs();\n```\n`yPlus > yPlusLam_` 时，\n$$\nv^2 = u\\_\\tau^2 \\cdot \\left[ \\frac{C\\_{v2}}{\\kappa}\\ln(y^+) + B\\_{v2} \\right]\n$$\n与文献中的无量纲形式 $(\\overline{v^2})^{^+} = \\frac{C\\_{v2}}{\\kappa}\\ln(y^+) + B\\_{v2} $ 一致。\n\n`yPlus < yPlusLam\\_` 时，\n$$\nv^2 = u\\_\\tau^2 \\cdot C\\_{v2}(y^+)^2\n$$\n与无量纲形式 $(\\overline{v^2})^{^+} = C\\_{v2}(y^+)^2$ 一致。\n+ $f$ 的壁函数\n```\nforAll(f, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar uTau = Cmu25*sqrt(k[faceCellI]);\n\n        scalar yPlus = uTau*y[faceI]/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            scalar N = 6.0;\n            scalar v2c = v2[faceCellI];\n            scalar epsc = epsilon[faceCellI];\n            scalar kc = k[faceCellI];\n\n            f[faceI] = N*v2c*epsc/(sqr(kc) + ROOTVSMALL);\n            f[faceI] /= sqr(uTau) + ROOTVSMALL;\n        }\n        else\n        {\n            f[faceI] = 0.0;\n        }\n    }\n```\n`yPlus > yPlusLam_` 时，\n$$\nf = \\frac{N \\cdot v^2\\cdot \\varepsilon}{k^2 u\\_\\tau^2}\n$$\n这似乎与文献中的无量纲形式\n$$\nf^+ = N \\frac{(\\overline{v^2})^{^+}}{(k^+)^2}\\varepsilon^+\n$$\n不一致！是 bug 还是我推导出错了？存疑...\n\n`yPlus < yPlusLam_` 时，文献给出的公式是\n$$\nf^+ = \\frac{-4(6-N)(\\overline{v^2})^{^+}}{\\varepsilon^+ (y^+)^4}\n$$\n当 `N=6` 时，可以得到 $f^+ = 0$ 。\n\n按理说，$v^2$ 和 $f$ 应该跟 $\\varepsilon$ 和 $\\omega$ 那样（见后文），计算第一层网格内的值，并且考虑一个网格有多个边界面的情形。OpenFOAM 目前计算的是每一个边界面元上的值，不知道这两种方式对结果有多大影响。\n","source":"_posts/wallFunctions2.md","raw":"title: \"OpenFOAM 中的壁面函数（二）\"\ndate: 2016-04-25 00:43:29\ncomments: true\ntags:\n- wall functions\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n这篇来看看计算湍动能 $k$ 的壁面函数。\n\n<!--more-->\n\n##### 2. 湍流动能 $k$ 的壁面函数\nOpenFOAM 中提供了两种 $k$ 的壁面函数， `kqRWallFunction` 和 `kLowReWallFunction` 。\n+ `kqRWallFunction` \n其实就是 `zeroGradient` ，无需多言。除非使用 $v^2\\text{-}f$ 模型，一般情况下 $k$ 应该使用这个边界条件。\n\n+ `kLowReWallFunction`\n这个壁面函数应该是可以用于低雷诺数模型的。该壁面函数继承自 `fixedValue` ：\n```\nclass kLowReWallFunctionFvPatchScalarField\n:\n    public fixedValueFvPatchField<scalar>\n{\nprotected:\n       //- Cmu coefficient\n        scalar Cmu_;\n\n        //- Von Karman constant\n        scalar kappa_;\n\n        //- E coefficient\n        scalar E_;\n\n        //- Ceps2 coefficient\n        scalar Ceps2_;\n\n        //- Y+ at the edge of the laminar sublayer\n        scalar yPlusLam_;\n        ......\n        \nkLowReWallFunctionFvPatchScalarField::kLowReWallFunctionFvPatchScalarField\n(\n    const fvPatch& p,\n    const DimensionedField<scalar, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fixedValueFvPatchField<scalar>(p, iF, dict),\n    Cmu_(dict.lookupOrDefault<scalar>(\"Cmu\", 0.09)),\n    kappa_(dict.lookupOrDefault<scalar>(\"kappa\", 0.41)),\n    E_(dict.lookupOrDefault<scalar>(\"E\", 9.8)),\n    Ceps2_(dict.lookupOrDefault<scalar>(\"Ceps2\", 1.9)),\n    yPlusLam_(yPlusLam(kappa_, E_))\n    {\n        checkType();\n    }\n\n}\n```\n核心的函数是以下两个：\n```cpp\nscalar kLowReWallFunctionFvPatchScalarField::yPlusLam\n(\n    const scalar kappa,\n    const scalar E\n)\n{\n    scalar ypl = 11.0;\n\n    for (int i=0; i<10; i++)\n    {\n        ypl = log(max(E*ypl, 1))/kappa;\n    }\n\n    return ypl;\n}\nvoid kLowReWallFunctionFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    const label patchI = patch().index();\n\n    const turbulenceModel& turbulence =\n        db().lookupObject<turbulenceModel>(\"turbulenceModel\");\n    const scalarField& y = turbulence.y()[patchI];\n\n    const tmp<volScalarField> tk = turbulence.k();\n    const volScalarField& k = tk();\n\n    const tmp<volScalarField> tnu = turbulence.nu();\n    const scalarField& nuw = tnu().boundaryField()[patchI];\n\n    const scalar Cmu25 = pow025(Cmu_);\n\n    scalarField& kw = *this; // 更新 kw 相当于更新壁面上的 k 值。\n\n    // Set k wall values\n    forAll(kw, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar uTau = Cmu25*sqrt(k[faceCellI]);\n\n        scalar yPlus = uTau*y[faceI]/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            scalar Ck = -0.416;\n            scalar Bk = 8.366;\n            kw[faceI] = Ck/kappa_*log(yPlus) + Bk;\n        }\n        else\n        {\n            scalar C = 11.0;\n            scalar Cf = (1.0/sqr(yPlus + C) + 2.0*yPlus/pow3(C) - 1.0/sqr(C));\n            kw[faceI] = 2400.0/sqr(Ceps2_)*Cf;\n        }\n\n        kw[faceI] *= sqr(uTau);\n    }\n\n    fixedValueFvPatchField<scalar>::updateCoeffs();\n\n    // TODO: perform averaging for cells sharing more than one boundary face\n}\n```\n先在函数里计算 `ypl` 的值， `updateCoeffs` 函数里根据 `yPlus` 与这个 `ypl` 的值来相对大小而采取不同的方法来计算壁面上的 $k\\_w$。 `ypl` 的计算是一个迭代过程\n$$\nypl = \\frac{\\log(\\max(E*ypl,1.0))}{\\kappa}\n$$\n初始值为 `ypl = 11.0`，迭代10次，最终结果应该是 `ypl = 11.5301073043272`。\n$y^+$ 定义为：\n$$\nu\\_\\tau = C\\_\\mu^{1/4 }\\sqrt{k\\_c} \\\\\ny^+ = \\frac{u\\_\\tau \\cdot y}{\\nu\\_w}\n$$\n壁面上的k计算方法如下：如果 $y^+ > ypl$，则\n$$\nk^+ \\_w = \\frac{C\\_k}{\\kappa}\\ln(y^+) + B\\_k\n$$\n否则\n$$\nk^+ \\_w = \\frac{2400}{C\\_{eps2}^2}\\cdot \\left[ \\frac{1}{(y^+ + C)^2} + \\frac{2y^+}{C^3} - \\frac{1}{C^2}\\right ]\n$$\n最终，壁面上的值为 $k\\_w=k^+ \\_w u\\_\\tau ^2 =k^+ \\_w C\\_\\mu^{1/2}k\\_c$ 。\n以上公式中，下标 $c$ 表示壁面单元所述网格的值，下标 $w$ 表示当前壁面上的值。\n这个壁面函数参考文献 \"Kalitzin, G., Medic, G., Iaccarino, G., Durbin, P., 2005. Near-wall behavior of RANS turbulence models and implications for wall functions. J. Comput. Phys. 204, 265–291. doi:10.1016/j.jcp.2004.10.018\"，是为 $v^2\\text{-}f$ 模型设计的。 \n\n##### $v^2$ 和 $f$ 的壁面函数\n上面提到了 $v^2\\text{-}f$ 模型，所以这里顺便来看看$v^2$ 和 $f$ 的壁面函数。这里参考的也是上面提到的那篇参考文献。\n\n+ $v^2$ 的壁函数\n```\nforAll(v2, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar uTau = Cmu25*sqrt(k[faceCellI]);\n\n        scalar yPlus = uTau*y[faceI]/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            scalar Cv2 = 0.193;\n            scalar Bv2 = -0.94;\n            v2[faceI] = Cv2/kappa_*log(yPlus) + Bv2;\n        }\n        else\n        {\n            scalar Cv2 = 0.193;\n            v2[faceI] = Cv2*pow4(yPlus);\n        }\n\n        v2[faceI] *= sqr(uTau);\n    }\n\n    fixedValueFvPatchField<scalar>::updateCoeffs();\n```\n`yPlus > yPlusLam_` 时，\n$$\nv^2 = u\\_\\tau^2 \\cdot \\left[ \\frac{C\\_{v2}}{\\kappa}\\ln(y^+) + B\\_{v2} \\right]\n$$\n与文献中的无量纲形式 $(\\overline{v^2})^{^+} = \\frac{C\\_{v2}}{\\kappa}\\ln(y^+) + B\\_{v2} $ 一致。\n\n`yPlus < yPlusLam\\_` 时，\n$$\nv^2 = u\\_\\tau^2 \\cdot C\\_{v2}(y^+)^2\n$$\n与无量纲形式 $(\\overline{v^2})^{^+} = C\\_{v2}(y^+)^2$ 一致。\n+ $f$ 的壁函数\n```\nforAll(f, faceI)\n    {\n        label faceCellI = patch().faceCells()[faceI];\n\n        scalar uTau = Cmu25*sqrt(k[faceCellI]);\n\n        scalar yPlus = uTau*y[faceI]/nuw[faceI];\n\n        if (yPlus > yPlusLam_)\n        {\n            scalar N = 6.0;\n            scalar v2c = v2[faceCellI];\n            scalar epsc = epsilon[faceCellI];\n            scalar kc = k[faceCellI];\n\n            f[faceI] = N*v2c*epsc/(sqr(kc) + ROOTVSMALL);\n            f[faceI] /= sqr(uTau) + ROOTVSMALL;\n        }\n        else\n        {\n            f[faceI] = 0.0;\n        }\n    }\n```\n`yPlus > yPlusLam_` 时，\n$$\nf = \\frac{N \\cdot v^2\\cdot \\varepsilon}{k^2 u\\_\\tau^2}\n$$\n这似乎与文献中的无量纲形式\n$$\nf^+ = N \\frac{(\\overline{v^2})^{^+}}{(k^+)^2}\\varepsilon^+\n$$\n不一致！是 bug 还是我推导出错了？存疑...\n\n`yPlus < yPlusLam_` 时，文献给出的公式是\n$$\nf^+ = \\frac{-4(6-N)(\\overline{v^2})^{^+}}{\\varepsilon^+ (y^+)^4}\n$$\n当 `N=6` 时，可以得到 $f^+ = 0$ 。\n\n按理说，$v^2$ 和 $f$ 应该跟 $\\varepsilon$ 和 $\\omega$ 那样（见后文），计算第一层网格内的值，并且考虑一个网格有多个边界面的情形。OpenFOAM 目前计算的是每一个边界面元上的值，不知道这两种方式对结果有多大影响。\n","slug":"wallFunctions2","published":1,"updated":"2016-04-25T02:49:07.183Z","layout":"post","photos":[],"link":"","_id":"cioiqegag000gz8mb5627c031"},{"title":"OpenFOAM 中的壁面函数（一）","date":"2016-04-24T16:36:27.000Z","comments":1,"_content":"\n本系列来看看 OpenFOAM 中的壁面函数。壁面函数的本质，是边界条件。这里主要来看看壁面函数的基本原理，OpenFOAM 中实现了的壁面函数，以及选择壁面函数的一些参考依据。\n\n<!--more-->\n\n##### 1. 壁面函数的基本原理\n\n湍流模拟中，需要对近壁区域进行处理。一般来讲，壁面处理方法包含两类，一类是使用很细的网格，使靠近壁面的第一层网格在粘性层内（$y^+ <1$），然后里可以直接解析到粘性层的低雷诺湍流模型；另一类，不直接解析粘性层，而是将第一层网格设置在对数区（$y^+ > 30$），然后用经验公式来将粘性层和对数区关联起来。下图是一个典型的壁面附近的 $U^+ \\text{-} y^+$ 关系图。\n![壁面律](/image/wallFunctions/Law_of_the_wall.png)\n图片来自 [Wikipedia:Law of the wall ](https://en.wikipedia.org/wiki/Law_of_the_wall)。\n在粘性层，满足如下关系\n$$\nu^+ = y^+\n$$\n而在对数区，则满足\n$$\nU^+ = \\frac{1}{\\kappa}\\ln(Ey^+)\n$$\n其中 $U^+ = U/u\\_\\tau$， $y^+ = yu\\_\\tau/\\nu$， $u\\_\\tau = \\sqrt{\\tau\\_w/\\rho}$，$\\kappa\\approx 0.41$，$E \\approx 9.8$，$y$ 表示与壁面的距离。\n\n本篇以标准壁面函数法来讨论一下壁面函数方法的基本原理，以及壁面函数在 OpenFOAM 中的实现。下面的讨论，先局限在 $k-\\varepsilon$ 模型，且第一层网格在对数区的情形。\n先来看一下壁面函数方法需要解决什么问题。\n有限体积方法中，扩散项的离散可以表示如下：\n$$\n\\nabla \\cdot (\\nu \\nabla U) = \\sum\\_f \\left [\\nu\\_f \\cdot (\\nabla U)\\_f \\right]\n$$\n当 $f$ 表示的是壁面边界单元时，这时就需要知道在壁面上的速度梯度 $(\\nabla U)\\_f$。壁面上一般对速度 $U$ 采用无滑移条件，如何得到正确的壁面速度梯度，这就是一个问题。这个问题有两个解决思路，一是通过实验或者 DNS 模拟等，得到一条连续的 $U-y$ 曲线，然后从这个曲线求壁面上的导数 $dU/dy$ 来得到壁面上的速度梯度；还有一种思路是，由于最终需要得到的是正确的 $\\nu\\_f \\cdot (\\nabla U)\\_f$ ，即壁面上的剪应力，虽然\n$$\n\\tau\\_w = \\nu \\cdot \\frac{\\partial U}{\\partial n}\\left. \\right|\\_w \\neq \\nu \\frac{U\\_p-U\\_w}{y}\n$$\n其中 $U\\_p$ 表示第一层网格中心的速度，$U\\_w$ 表示壁面上的速度。 \n但是，可以构造一个壁面上的有效粘度 $\\nu\\_{eff}$，以使下式成立\n$$\n\\tau\\_w = \\nu \\cdot  \\frac{\\partial U}{\\partial n} \\left. \\right |\\_w = \\nu\\_{eff} \\frac{U\\_p-U\\_w}{y} = (\\nu + \\nu\\_t ) \\cdot \\frac{U\\_p-U\\_w}{y}\n$$\n\n后一种解决方法的好处是，不需要修改动量方程，直接使用 $\\frac{U\\_p-U\\_w}{y}$ 来代替 $\\frac{\\partial U}{\\partial n} \\left. \\right |\\_w  $，然后通过设置合适的湍流粘度 $\\nu\\_t$ 的边界条件来修正壁面应力 $\\tau\\_w$。\n\n另一方面，在对数区，$k^+$ 是常数\n$$\nk^+ = \\frac{1}{\\sqrt{C\\_\\mu}} \\\\\\\\\n$$\n其中 $k^+ = k/u\\_\\tau^2$。\n由\n$$\nk^+ = \\frac{1}{\\sqrt{C\\_\\mu}} = k/u\\_\\tau^2\n$$\n得\n$$\nu\\_\\tau = C\\_\\mu^{1/4}k^{1/2}\n$$\n于是\n$$\n\\tau\\_w = \\rho u\\_\\tau^2 = \\rho u\\_\\tau \\cdot \\frac{U}{U^+} = \\frac{\\rho u\\_\\tau (U\\_p-U\\_w)}{\\frac{1}{\\kappa}\\ln(Ey^+)}\n$$\n若令 \n$$\n\\nu\\_{eff} = \\frac{u\\_\\tau y}{\\frac{1}{\\kappa}\\ln(Ey^+)}\n$$\n则\n$$\n\\tau\\_w = \\rho \\nu\\_{eff}\\cdot \\frac{U\\_p-U\\_w}{y}\n$$\n这正是上文提到的第二种解决壁面速度问题的形式。\n而\n$$\n\\nu\\_{eff} = \\frac{ u\\_\\tau y}{\\frac{1}{\\kappa}\\ln(Ey^+)} = \\frac{ y^+ \\nu}{\\frac{1}{\\kappa}\\ln(Ey^+)} = \\nu + \\nu\\_{tw}\n$$\n于是得到壁面上的湍流粘度为\n$$\n\\nu\\_{tw} = \\nu \\cdot \\left(\\frac{\\kappa y^+}{\\ln(Ey^+)} -1 \\right)\n$$\n\n$y^+$ 可以通过不同的方式来得到，具体的计算方法，见后文的 `nutWallFunctions` 部分。\n\n\n除了得到壁面上的等效湍流粘度，还需要计算靠近壁面第一层网格的湍动能生成和湍动能耗散项。\n\n湍动能生成项计算如下：\n$$\nG \\approx \\tau\\_w\\cdot \\frac{\\partial (U\\_p -U\\_w)}{\\partial y}\n$$\n由速度的壁面律\n$$\nU^+ = \\frac{U\\_p - U\\_w}{u\\_\\tau} = \\frac{1}{\\kappa}\\ln(Ey^+) = \\frac{1}{\\kappa} \\ln(E\\frac{yu\\_\\tau}{\\nu})\n$$\n注意，$G$ 求的是第一层网格内的值，所以，由\n$$\nU\\_p -U\\_w = \\frac{u\\_\\tau}{\\kappa} \\ln(E\\frac{yu\\_\\tau}{\\nu})\n$$\n可以求得第一层网格内的梯度\n$$\n\\frac{\\partial (U\\_p -U\\_w)}{\\partial y} \\left. \\right|\\_p = \\frac{u\\_\\tau}{\\kappa y\\_p}\n$$\n于是\n$$\nG = \\tau\\_w \\cdot \\frac{u\\_\\tau}{\\kappa y\\_p} \n$$\n注意，这里的 $\\frac{U\\_p-U\\_w}{d}$，其实是速度在壁面法向方向的梯度的近似值，这一点见上文 $\\nu\\_t$ 的边界条件部分。\n\n再来看 $\\varepsilon$，$\\varepsilon$ 的计算基于第一层网格内的湍动生成与湍动能耗散项守恒的假设，即\n$$\n \\rho \\varepsilon\\_p = G = \\tau\\_w \\cdot \\frac{u\\_\\tau}{\\kappa y\\_p} =\\rho\\cdot \\frac{u\\_\\tau^3}{\\kappa y\\_p}\n$$\n于是得\n$$\n\\varepsilon\\_p = \\frac{u\\_\\tau^3}{\\kappa y\\_p} = \\frac{C\\_\\mu^{3/4}k\\_p^{3/2}}{\\kappa y\\_p}\n$$\n\n至于 $k$，一般认为当第一层网格位于对数区时，不需要在壁面上对 $k$ 加任何限制，用零梯度边界条件即可。\n\n##### 2. 在 OpenFOAM 中的实现\n在 OpenFOAM 中，$k$，$\\varepsilon$ 和 $\\nu\\_t$ 分别有对应的边界条件可以选择，壁面函数的实现是在这些边界条件里进行的。具体地说， `k***WallFunction` 用于指定 $k$ 的边界条件， `epsilon***WallFunction` 用于计算 $\\varepsilon$ 和 $G$ 在第一层网格内的值， `nut***WallFunction` 用来计算 $\\nu\\_t$ 在壁面上的值。 还有就是一个要关心的问题是这些边界条件的调用顺序，这需要通过湍流模型的一段代码来说明，以 `kEpsilon` 为例：\n```\nvoid kEpsilon::correct()\n{\n    RASModel::correct();\n    if (!turbulence_)\n    {\n        return;\n    }\n    volScalarField G(GName(), nut_*2*magSqr(symm(fvc::grad(U_))));\n\n    // Update epsilon and G at the wall\n    epsilon_.boundaryField().updateCoeffs();\n\n    // Dissipation equation\n    tmp<fvScalarMatrix> epsEqn\n    (\n        fvm::ddt(epsilon_)\n      + fvm::div(phi_, epsilon_)\n      - fvm::laplacian(DepsilonEff(), epsilon_)\n     ==\n        C1_*G*epsilon_/k_\n      - fvm::Sp(C2_*epsilon_/k_, epsilon_)\n    );\n\n    epsEqn().relax();\n    epsEqn().boundaryManipulate(epsilon_.boundaryField());\n\n    solve(epsEqn);\n    bound(epsilon_, epsilonMin_);\n\n    // Turbulent kinetic energy equation\n    tmp<fvScalarMatrix> kEqn\n    (\n        fvm::ddt(k_)\n      + fvm::div(phi_, k_)\n      - fvm::laplacian(DkEff(), k_)\n     ==\n        G\n      - fvm::Sp(epsilon_/k_, k_)\n    );\n    kEqn().relax();\n    solve(kEqn);\n    bound(k_, kMin_);\n\n    // Re-calculate viscosity\n    nut_ = Cmu_*sqr(k_)/epsilon_;\n    nut_.correctBoundaryConditions();\n}\n```\n从上述代码，可以将湍流模型的具体计算过程归纳如下：\n1. 计算湍动能生成项 $G$，并修正 $G$ 在第一层网格的值。修正是通过 `epsilon_.boundaryField().updateCoeffs();` 来实现的，这里调用 `epsilon` 的边界条件的 `updateCoeffs` 函数，实现的操作是修正 $G$ 和 $\\varepsilon$ 在第一层网格的值。\n2. 利用更新的 $G$ 构建 `epsEqn`，然后修改 `epsEqn`（ `epsEqn().boundaryManipulate(epsilon_.boundaryField());` ），这样做的目的是保证在下一步 `solve(epsEqn)`的时候，`epsilonWallFunction` 类型的边界所属的网格的值不会变化，而是保持在 `epsilon_.boundaryField().updateCoeffs();` 这一步里设置的值（参考 [cfd-online 的这个帖子](http://www.cfd-online.com/Forums/openfoam-solving/132703-boundarymanipulate.html)）。\n3. 求解 `epsEqn`，得到更新的 $\\varepsilon$ 场。\n4. 利用更新的 $\\varepsilon$ 场构建并求解 `kEqn`，得到更新的 $k$ 场。\n5. 计算 $\\nu\\_t$，并更新 $\\nu\\_t$ 在边界上的值（`nut_.correctBoundaryConditions()`） \n\n至于具体的 $k$，$\\varepsilon$，以及 $\\nu\\_t$ 的边界条件的实现，见后文。 \n\n**参考**\n1. The Finite Volume Method in Computational Fluid Dynamics An Advanced Introduction with OpenFOAM® and Matlab®\n2. http://www.slideshare.net/fumiyanozaki96/openfoam-36426892\n","source":"_posts/wallFunctions1.md","raw":"title: \"OpenFOAM 中的壁面函数（一）\"\ndate: 2016-04-25 00:36:27\ncomments: true\ntags:\n- wall functions\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n本系列来看看 OpenFOAM 中的壁面函数。壁面函数的本质，是边界条件。这里主要来看看壁面函数的基本原理，OpenFOAM 中实现了的壁面函数，以及选择壁面函数的一些参考依据。\n\n<!--more-->\n\n##### 1. 壁面函数的基本原理\n\n湍流模拟中，需要对近壁区域进行处理。一般来讲，壁面处理方法包含两类，一类是使用很细的网格，使靠近壁面的第一层网格在粘性层内（$y^+ <1$），然后里可以直接解析到粘性层的低雷诺湍流模型；另一类，不直接解析粘性层，而是将第一层网格设置在对数区（$y^+ > 30$），然后用经验公式来将粘性层和对数区关联起来。下图是一个典型的壁面附近的 $U^+ \\text{-} y^+$ 关系图。\n![壁面律](/image/wallFunctions/Law_of_the_wall.png)\n图片来自 [Wikipedia:Law of the wall ](https://en.wikipedia.org/wiki/Law_of_the_wall)。\n在粘性层，满足如下关系\n$$\nu^+ = y^+\n$$\n而在对数区，则满足\n$$\nU^+ = \\frac{1}{\\kappa}\\ln(Ey^+)\n$$\n其中 $U^+ = U/u\\_\\tau$， $y^+ = yu\\_\\tau/\\nu$， $u\\_\\tau = \\sqrt{\\tau\\_w/\\rho}$，$\\kappa\\approx 0.41$，$E \\approx 9.8$，$y$ 表示与壁面的距离。\n\n本篇以标准壁面函数法来讨论一下壁面函数方法的基本原理，以及壁面函数在 OpenFOAM 中的实现。下面的讨论，先局限在 $k-\\varepsilon$ 模型，且第一层网格在对数区的情形。\n先来看一下壁面函数方法需要解决什么问题。\n有限体积方法中，扩散项的离散可以表示如下：\n$$\n\\nabla \\cdot (\\nu \\nabla U) = \\sum\\_f \\left [\\nu\\_f \\cdot (\\nabla U)\\_f \\right]\n$$\n当 $f$ 表示的是壁面边界单元时，这时就需要知道在壁面上的速度梯度 $(\\nabla U)\\_f$。壁面上一般对速度 $U$ 采用无滑移条件，如何得到正确的壁面速度梯度，这就是一个问题。这个问题有两个解决思路，一是通过实验或者 DNS 模拟等，得到一条连续的 $U-y$ 曲线，然后从这个曲线求壁面上的导数 $dU/dy$ 来得到壁面上的速度梯度；还有一种思路是，由于最终需要得到的是正确的 $\\nu\\_f \\cdot (\\nabla U)\\_f$ ，即壁面上的剪应力，虽然\n$$\n\\tau\\_w = \\nu \\cdot \\frac{\\partial U}{\\partial n}\\left. \\right|\\_w \\neq \\nu \\frac{U\\_p-U\\_w}{y}\n$$\n其中 $U\\_p$ 表示第一层网格中心的速度，$U\\_w$ 表示壁面上的速度。 \n但是，可以构造一个壁面上的有效粘度 $\\nu\\_{eff}$，以使下式成立\n$$\n\\tau\\_w = \\nu \\cdot  \\frac{\\partial U}{\\partial n} \\left. \\right |\\_w = \\nu\\_{eff} \\frac{U\\_p-U\\_w}{y} = (\\nu + \\nu\\_t ) \\cdot \\frac{U\\_p-U\\_w}{y}\n$$\n\n后一种解决方法的好处是，不需要修改动量方程，直接使用 $\\frac{U\\_p-U\\_w}{y}$ 来代替 $\\frac{\\partial U}{\\partial n} \\left. \\right |\\_w  $，然后通过设置合适的湍流粘度 $\\nu\\_t$ 的边界条件来修正壁面应力 $\\tau\\_w$。\n\n另一方面，在对数区，$k^+$ 是常数\n$$\nk^+ = \\frac{1}{\\sqrt{C\\_\\mu}} \\\\\\\\\n$$\n其中 $k^+ = k/u\\_\\tau^2$。\n由\n$$\nk^+ = \\frac{1}{\\sqrt{C\\_\\mu}} = k/u\\_\\tau^2\n$$\n得\n$$\nu\\_\\tau = C\\_\\mu^{1/4}k^{1/2}\n$$\n于是\n$$\n\\tau\\_w = \\rho u\\_\\tau^2 = \\rho u\\_\\tau \\cdot \\frac{U}{U^+} = \\frac{\\rho u\\_\\tau (U\\_p-U\\_w)}{\\frac{1}{\\kappa}\\ln(Ey^+)}\n$$\n若令 \n$$\n\\nu\\_{eff} = \\frac{u\\_\\tau y}{\\frac{1}{\\kappa}\\ln(Ey^+)}\n$$\n则\n$$\n\\tau\\_w = \\rho \\nu\\_{eff}\\cdot \\frac{U\\_p-U\\_w}{y}\n$$\n这正是上文提到的第二种解决壁面速度问题的形式。\n而\n$$\n\\nu\\_{eff} = \\frac{ u\\_\\tau y}{\\frac{1}{\\kappa}\\ln(Ey^+)} = \\frac{ y^+ \\nu}{\\frac{1}{\\kappa}\\ln(Ey^+)} = \\nu + \\nu\\_{tw}\n$$\n于是得到壁面上的湍流粘度为\n$$\n\\nu\\_{tw} = \\nu \\cdot \\left(\\frac{\\kappa y^+}{\\ln(Ey^+)} -1 \\right)\n$$\n\n$y^+$ 可以通过不同的方式来得到，具体的计算方法，见后文的 `nutWallFunctions` 部分。\n\n\n除了得到壁面上的等效湍流粘度，还需要计算靠近壁面第一层网格的湍动能生成和湍动能耗散项。\n\n湍动能生成项计算如下：\n$$\nG \\approx \\tau\\_w\\cdot \\frac{\\partial (U\\_p -U\\_w)}{\\partial y}\n$$\n由速度的壁面律\n$$\nU^+ = \\frac{U\\_p - U\\_w}{u\\_\\tau} = \\frac{1}{\\kappa}\\ln(Ey^+) = \\frac{1}{\\kappa} \\ln(E\\frac{yu\\_\\tau}{\\nu})\n$$\n注意，$G$ 求的是第一层网格内的值，所以，由\n$$\nU\\_p -U\\_w = \\frac{u\\_\\tau}{\\kappa} \\ln(E\\frac{yu\\_\\tau}{\\nu})\n$$\n可以求得第一层网格内的梯度\n$$\n\\frac{\\partial (U\\_p -U\\_w)}{\\partial y} \\left. \\right|\\_p = \\frac{u\\_\\tau}{\\kappa y\\_p}\n$$\n于是\n$$\nG = \\tau\\_w \\cdot \\frac{u\\_\\tau}{\\kappa y\\_p} \n$$\n注意，这里的 $\\frac{U\\_p-U\\_w}{d}$，其实是速度在壁面法向方向的梯度的近似值，这一点见上文 $\\nu\\_t$ 的边界条件部分。\n\n再来看 $\\varepsilon$，$\\varepsilon$ 的计算基于第一层网格内的湍动生成与湍动能耗散项守恒的假设，即\n$$\n \\rho \\varepsilon\\_p = G = \\tau\\_w \\cdot \\frac{u\\_\\tau}{\\kappa y\\_p} =\\rho\\cdot \\frac{u\\_\\tau^3}{\\kappa y\\_p}\n$$\n于是得\n$$\n\\varepsilon\\_p = \\frac{u\\_\\tau^3}{\\kappa y\\_p} = \\frac{C\\_\\mu^{3/4}k\\_p^{3/2}}{\\kappa y\\_p}\n$$\n\n至于 $k$，一般认为当第一层网格位于对数区时，不需要在壁面上对 $k$ 加任何限制，用零梯度边界条件即可。\n\n##### 2. 在 OpenFOAM 中的实现\n在 OpenFOAM 中，$k$，$\\varepsilon$ 和 $\\nu\\_t$ 分别有对应的边界条件可以选择，壁面函数的实现是在这些边界条件里进行的。具体地说， `k***WallFunction` 用于指定 $k$ 的边界条件， `epsilon***WallFunction` 用于计算 $\\varepsilon$ 和 $G$ 在第一层网格内的值， `nut***WallFunction` 用来计算 $\\nu\\_t$ 在壁面上的值。 还有就是一个要关心的问题是这些边界条件的调用顺序，这需要通过湍流模型的一段代码来说明，以 `kEpsilon` 为例：\n```\nvoid kEpsilon::correct()\n{\n    RASModel::correct();\n    if (!turbulence_)\n    {\n        return;\n    }\n    volScalarField G(GName(), nut_*2*magSqr(symm(fvc::grad(U_))));\n\n    // Update epsilon and G at the wall\n    epsilon_.boundaryField().updateCoeffs();\n\n    // Dissipation equation\n    tmp<fvScalarMatrix> epsEqn\n    (\n        fvm::ddt(epsilon_)\n      + fvm::div(phi_, epsilon_)\n      - fvm::laplacian(DepsilonEff(), epsilon_)\n     ==\n        C1_*G*epsilon_/k_\n      - fvm::Sp(C2_*epsilon_/k_, epsilon_)\n    );\n\n    epsEqn().relax();\n    epsEqn().boundaryManipulate(epsilon_.boundaryField());\n\n    solve(epsEqn);\n    bound(epsilon_, epsilonMin_);\n\n    // Turbulent kinetic energy equation\n    tmp<fvScalarMatrix> kEqn\n    (\n        fvm::ddt(k_)\n      + fvm::div(phi_, k_)\n      - fvm::laplacian(DkEff(), k_)\n     ==\n        G\n      - fvm::Sp(epsilon_/k_, k_)\n    );\n    kEqn().relax();\n    solve(kEqn);\n    bound(k_, kMin_);\n\n    // Re-calculate viscosity\n    nut_ = Cmu_*sqr(k_)/epsilon_;\n    nut_.correctBoundaryConditions();\n}\n```\n从上述代码，可以将湍流模型的具体计算过程归纳如下：\n1. 计算湍动能生成项 $G$，并修正 $G$ 在第一层网格的值。修正是通过 `epsilon_.boundaryField().updateCoeffs();` 来实现的，这里调用 `epsilon` 的边界条件的 `updateCoeffs` 函数，实现的操作是修正 $G$ 和 $\\varepsilon$ 在第一层网格的值。\n2. 利用更新的 $G$ 构建 `epsEqn`，然后修改 `epsEqn`（ `epsEqn().boundaryManipulate(epsilon_.boundaryField());` ），这样做的目的是保证在下一步 `solve(epsEqn)`的时候，`epsilonWallFunction` 类型的边界所属的网格的值不会变化，而是保持在 `epsilon_.boundaryField().updateCoeffs();` 这一步里设置的值（参考 [cfd-online 的这个帖子](http://www.cfd-online.com/Forums/openfoam-solving/132703-boundarymanipulate.html)）。\n3. 求解 `epsEqn`，得到更新的 $\\varepsilon$ 场。\n4. 利用更新的 $\\varepsilon$ 场构建并求解 `kEqn`，得到更新的 $k$ 场。\n5. 计算 $\\nu\\_t$，并更新 $\\nu\\_t$ 在边界上的值（`nut_.correctBoundaryConditions()`） \n\n至于具体的 $k$，$\\varepsilon$，以及 $\\nu\\_t$ 的边界条件的实现，见后文。 \n\n**参考**\n1. The Finite Volume Method in Computational Fluid Dynamics An Advanced Introduction with OpenFOAM® and Matlab®\n2. http://www.slideshare.net/fumiyanozaki96/openfoam-36426892\n","slug":"wallFunctions1","published":1,"updated":"2016-04-26T15:12:44.864Z","layout":"post","photos":[],"link":"","_id":"cioiqegak000kz8mbdady6kfo"},{"title":"vim Extension for OpenFOAM","date":"2015-08-16T06:59:23.000Z","comments":1,"_content":"\n本篇介绍一个[vim 插件](https://bitbucket.org/shor-ty/vimextensionopenfoam)，该插件会自动检测某个文件是否是 OpenFOAM 的设置文件，然后根据预设的颜色显示方案来对大部分的 OpenFOAM 关键字进行高亮显示。单凭这一个特性，还不至让我专门写一篇博文来介绍它，真正让我觉得实用的是另外一个附带的特性：关键字补全。安装此插件以后，可以在用 vim 编辑 OpenFOAM 设置文件的时候对 OpenFOAM 的很多关键字，包括边界条件，湍流模型，离散格式等等进行自动补全，下面对这个插件和基本功能进行一个简单介绍。\n\n<!--more-->\n\n#### 1. 安装\n安装很简单，首先去 https://bitbucket.org/shor-ty/vimextensionopenfoam 下载，建议使用 git clone 来讲插件的代码下载到本地。然后，运行代码根目录下的安装脚本 `install`，就完成了安装。更详细的步骤以及颜色配置的选项，在插件项目的主页上也有介绍，这里不再赘述。\n\n#### 2. 基本特性\n该插件的基本特性就是对 OpenFOAM 关键字进行高亮显示，见下图：\n+ 图1：使用插件之前\n ![](/image/vimext/without_plugin.png)\n\n+ 图2：使用插件之后\n ![](/image/vimext/with_plugin.png)\n\n有了这个高亮显示后，就可以根据颜色看出设置文件里的关键词是否有错了。\n\n#### 3. 关键字补全\n关键字补全在实际使用中还是很有作用的，一来可以减少手动输入，二来可以很有效地减少键入错误。注意，按照插件作者的介绍，这个插件的主要功能是高亮显示 OpenFOAM 关键词，补全功能，则算是一个副产物。下面通过几个动画来显示补全功能：\n + 边界条件补全\n ![](/image/vimext/k.gif)\n ![](/image/vimext/k1.gif)\n\n + RAS 湍流模型  \n ![](/image/vimext/RAS.gif)\n\n + fvSchemes\n![](/image/vimext/fvSchemes.gif) \n + fvSolutions\n![](/image/vimext/fvSolutions.gif) \n    \n需要注意的是，这里我只使用了 vim 自带的最简单的补全方法：Ctrl n 和 Ctrl p。以上动画中，输入关键字的头几个字母，只需要按 ctrl n 或 ctrl p 就会显示出所有候选补全选项。\n\n但是，这个插件没有包括所有的 OpenFOAM 关键字，比如标准求解器以及 LES 湍流模型就没有包括。可以通过编辑 `~/.vim/syntax/foam256`目录下的相应设置文件进行修改来对其进行扩展。\n","source":"_posts/vimExtensionOpenFOAM.md","raw":"title: \"vim Extension for OpenFOAM\"\ndate: 2015-08-16 14:59:23\ncomments: true\ntags: \n    - OpenFOAM\n    - vim\ncategories:\n    - vim\n---\n\n本篇介绍一个[vim 插件](https://bitbucket.org/shor-ty/vimextensionopenfoam)，该插件会自动检测某个文件是否是 OpenFOAM 的设置文件，然后根据预设的颜色显示方案来对大部分的 OpenFOAM 关键字进行高亮显示。单凭这一个特性，还不至让我专门写一篇博文来介绍它，真正让我觉得实用的是另外一个附带的特性：关键字补全。安装此插件以后，可以在用 vim 编辑 OpenFOAM 设置文件的时候对 OpenFOAM 的很多关键字，包括边界条件，湍流模型，离散格式等等进行自动补全，下面对这个插件和基本功能进行一个简单介绍。\n\n<!--more-->\n\n#### 1. 安装\n安装很简单，首先去 https://bitbucket.org/shor-ty/vimextensionopenfoam 下载，建议使用 git clone 来讲插件的代码下载到本地。然后，运行代码根目录下的安装脚本 `install`，就完成了安装。更详细的步骤以及颜色配置的选项，在插件项目的主页上也有介绍，这里不再赘述。\n\n#### 2. 基本特性\n该插件的基本特性就是对 OpenFOAM 关键字进行高亮显示，见下图：\n+ 图1：使用插件之前\n ![](/image/vimext/without_plugin.png)\n\n+ 图2：使用插件之后\n ![](/image/vimext/with_plugin.png)\n\n有了这个高亮显示后，就可以根据颜色看出设置文件里的关键词是否有错了。\n\n#### 3. 关键字补全\n关键字补全在实际使用中还是很有作用的，一来可以减少手动输入，二来可以很有效地减少键入错误。注意，按照插件作者的介绍，这个插件的主要功能是高亮显示 OpenFOAM 关键词，补全功能，则算是一个副产物。下面通过几个动画来显示补全功能：\n + 边界条件补全\n ![](/image/vimext/k.gif)\n ![](/image/vimext/k1.gif)\n\n + RAS 湍流模型  \n ![](/image/vimext/RAS.gif)\n\n + fvSchemes\n![](/image/vimext/fvSchemes.gif) \n + fvSolutions\n![](/image/vimext/fvSolutions.gif) \n    \n需要注意的是，这里我只使用了 vim 自带的最简单的补全方法：Ctrl n 和 Ctrl p。以上动画中，输入关键字的头几个字母，只需要按 ctrl n 或 ctrl p 就会显示出所有候选补全选项。\n\n但是，这个插件没有包括所有的 OpenFOAM 关键字，比如标准求解器以及 LES 湍流模型就没有包括。可以通过编辑 `~/.vim/syntax/foam256`目录下的相应设置文件进行修改来对其进行扩展。\n","slug":"vimExtensionOpenFOAM","published":1,"updated":"2015-08-16T08:50:58.124Z","layout":"post","photos":[],"link":"","_id":"cioiqegb3000oz8mbvtahux4r"},{"title":"twoPhaseEulerFoam 全解读之三","date":"2015-06-27T12:39:01.000Z","comments":1,"_content":"\n本系列将对OpenFOAM-2.1.1 中的 `twoPhaseEulerFoam` 求解器进行完全解读，共分三部分：方程推导，代码解读，补充说明。本篇对 `twoPhaseEulerFoam` 中的 `alphaEqn.H` 进行详细地的解读，并作一些补充说明。\n\n<!--more-->\n\n### 2.3. alphaEqn\n前文提到，经过求解 `pEqn`，并修正速度`Ua`和`Ub`以后，总体的连续性便得到了保证。为了得到分散相的体积分率`alpha`，还需要利用得到的速度场来求解分散相的连续性方程，即`alphaEqn`。分散相连续性方程可以表达如下：\n$$\n\\frac{\\partial \\alpha\\_a}{\\partial t}+\\nabla \\cdot (\\alpha\\_a U\\_a)=0\n$$\n\n为了让每一项都写成守恒形式，并且保证$\\alpha_a$的有界性，Weller 将分散相连续性方程写成如下形式\n$$\n\\frac{\\partial \\alpha\\_a}{\\partial t}+\\nabla \\cdot (\\alpha_a U)+\\nabla \\cdot(U\\_r\\alpha\\_a(1-\\alpha\\_a))=0 \n$$\n其中\n$$\nU=\\alpha\\_a U\\_a + \\alpha\\_b U\\_b \\\\\\\\\nU\\_r=U\\_a-U\\_b\n$$\nOpenFOAM-2.1.1 的`alphaEqn`求解的正是 Weller 提出的形式。主要的代码如下：\n```\n fvScalarMatrix alphaEqn\n        (\n             fvm::ddt(alpha)\n           + fvm::div(phic, alpha, scheme)\n           + fvm::div(-fvc::flux(-phir, beta, schemer), alpha, schemer)\n        );\n\n```\n其中`phic`与`phir`的定义如下：\n```\nsurfaceScalarField phic(\"phic\", phi);\nsurfaceScalarField phir(\"phir\", phia - phib);\n\n```\n得到分散相体积分率后，连续相体积分率$\\alpha\\_b$则为$1-\\alpha\\_a$，如下\n```\n beta = scalar(1) - alpha;\n```\n\n## 3. 补充说明\n想必读者肯定发现了，我前面的代码解读相比于`twoPhaseEulerFoam`的源码其实省略了很多，总体上来讲，省略了三大块，一是跟`kineticTheory`相关的，二是跟`g0`相关的，三是`packingLimiter`，下面对这三部分进行一些补充说明。\n### 3.1 kineticTheory\n`kineticTheory`是 KTGF(Kinetic Theory of Granular Flow) 方法在OpenFOAM-2.1.1里的实现。KTGF 的主要作用是计算固相压力和固相粘度，以封闭前述的分散相动量方程，所以`kineticTheory`只有在模拟气固（液固）两相流时才需要开启。 `kineticTheory` 是否开启以及 KTGF 模型的参数需要在算例的`constant/kineticTheoryProperties`文件里进行设置。如果开启了`kineticTheory`，主要的影响如下：\n+ UEqn.H\n```\nif (kineticTheory.on())\n{\n    kineticTheory.solve(gradUaT);\n    nuEffa = kineticTheory.mua()/rhoa;\n}\n\n```\n 如果开启`kineticTheory`，则固相粘度`nuEffa`是由 KTGF 来计算得到，否则`nuEffa`是用一个简单的关联式来计算\n```\nelse\n{\n    nuEffa = sqr(Ct)*nutb + nua;\n}\n\n```\n 此外，`Rca`项也要加上额外的项\n```\nif (kineticTheory.on())\n{\n    Rca -= ((kineticTheory.lambda()/rhoa)*tr(gradUaT))*tensor(I);\n}\n```\n\n+ pEqn.H\n 如果`kineticTheory`开启了，则要在压力修正方程中加上额外的固相压力项。\n```\nif (kineticTheory.on())\n{\n    phiDraga -= rUaAf*fvc::snGrad(kineticTheory.pa()/rhoa)*mesh.magSf();\n}\n```\n\n有关 OpenFOAM 中使用的 KTGF 模型的理论可以参考[Derivation, Implementation, and Validation of Computer Simulation Models for Gas-Solid Fluidized Beds](http://repository.tudelft.nl/view/ir/uuid%3A919e2efa-5db2-40e6-9082-83b1416709a6/)，以及B.G.M. van Wachem 的其他相关论文。\n\n### 3.2 g0\n跟 `kineticTheory`一样，`g0` 也是只有在模拟气固（液固）两相流时才需要开启，相关的设置在`constant/ppProperties`里，当`g0`的值设置为大于零时，则跟`g0`相关的项会其作用，主要的影响如下：\n+ pEqn\n```\nif (g0.value() > 0.0)\n{\n    phiDraga -= ppMagf*fvc::snGrad(alpha)*mesh.magSf();\n}\n```\n 其中`ppMagf`的定义如下：\n ```\n ppMagf = rUaAf*fvc::interpolate\n (\n  (1.0/(rhoa*(alpha + scalar(0.0001))))\n  *g0*min(exp(preAlphaExp*(alpha - alphaMax)), expMax)\n ); \n ```\n 这一段的效果，相当于在[本系列第一篇](http://xiaopingqiu.github.io/2015/05/17/twoPhaseEulerFoam1/)最后的分散相动量方程的等式右边额外增加一项：\n $$\n -g0\\*min(e^{preAlphaExp\\*(\\alpha\\_a - \\alpha\\_{Max})},expMax)\\nabla \\alpha\\_a / (\\alpha\\_a \\rho\\_a)\n $$\n 可见，这一项的作用是给固相额外施加了一个力，可以认为是固相压力，这个力与$\\alpha\\_a$的梯度有关，且与梯度的方向相反。\n注意这一项的特点：`g0`, `preAlphaExp`, `alphaMax`, `expMax` 都是需要用户指定的参数，一般将`g0`, `preAlphaExp`和`expMax`设置为一个比较大的正整数（$10^3$的量级），当`alpha - alphaMax < 0`时，$e^{\\,preAlphaExp\\*(\\alpha\\_a - \\alpha\\_{Max})}$ 将是一个很小的数，此时整个增加的一项也将是一个较小的数，所以，`alpha - alphaMax < 0`时额外增加的那一项对动量方程的贡献很小。但是，当`alpha - alphaMax > 0`时，随着 `alpha` 偏离 `alphaMax` 越来越远，`exp(preAlphaExp*(alpha - alphaMax)`将迅速增大，`min(exp(preAlphaExp*(alpha - alphaMax)), expMax)`的值也将迅速增大，直到设定值`expMax`。可见，`g0`项的作用可以理解为为了防止固相过度堆积。气固系统中，固相的体积分率是有上限的，可以将`alphaMax`设置为这个上限值，当某个网格里的`alpha`超过设定的`alphaMax`时，就让固相迅速弹开，以防止固相体积分率过大。\n\n+ alphaEqn.H\nalphaEqn.H 里有两处跟 `g0` 有关的，\n一处是对 `phic` 和 `phir` 进行修正：\n```\nif (g0.value() > 0.0)\n{\n    surfaceScalarField alphaf(fvc::interpolate(alpha));\n    surfaceScalarField phipp(ppMagf*fvc::snGrad(alpha)*mesh.magSf());\n    phir += phipp;\n    phic += fvc::interpolate(alpha)*phipp;\n}\n```\n另一处是对 `alphaEqn` 进行修正：\n```\nif (g0.value() > 0.0)\n{\n    ppMagf = rUaAf*fvc::interpolate\n    (\n     (1.0/(rhoa*(alpha + scalar(0.0001))))\n     *g0*min(exp(preAlphaExp*(alpha - alphaMax)), expMax)\n     );\n    \n    alphaEqn -= fvm::laplacian\n    (\n     (fvc::interpolate(alpha) + scalar(0.0001))*ppMagf,\n     alpha,\n     \"laplacian(alphaPpMag,alpha)\"\n     );\n}\n```\n为什么当 `g0 > 0` 时，需要在 `alphaEqn` 中额外减去一个 `laplacian` 项呢？这里要结合上面提到的两段代码来进行分析。\n在对 `phic` 和 `phir` 进行修正这一段， `phic` 和 `phir` 分别加上了一项。将修正过的 `phic` 和 `phir` 代入到 `alphaEqn` 中，会导致 `alphaEqn` 与上文的分散相连续性方程\n$$\n\\frac{\\partial \\alpha\\_a}{\\partial t}+\\nabla \\cdot (\\alpha_a U)+\\nabla \\cdot(U\\_r\\alpha\\_a(1-\\alpha\\_a))=0 \n$$\n不一致，其中 `alphaEqn` 多出了几项：\n其中\n` fvm::div(phic, alpha, scheme) ` 比 $ \\nabla \\cdot (\\alpha_a U) $ 多了 ` fvm::div(alphaf*phipp, alpha, scheme) ` 一项，写成公式，就是 $ \\nabla \\cdot [\\alpha (\\alpha *\\mathrm{ppMagf}) \\nabla \\alpha] $ 。\n\n而 ` fvm::div(-fvc::flux(-phir, beta, schemer), alpha, schemer) ` 则比 $ \\nabla \\cdot (\\alpha_a U)+\\nabla \\cdot(U\\_r\\alpha\\_a(1-\\alpha\\_a) $ 多出了 ` fvm::div(-fvc::flux(-phipp, beta, schemer), alpha, schemer) ` 一项，写成公式就是 $ \\nabla \\cdot [\\alpha (\\beta \\*\\mathrm{ppMagf}) \\nabla \\alpha] $ 。\n多出的这两项加起来，为\n$$\n\\nabla \\cdot [\\alpha  *\\mathrm{ppMagf} \\  \\nabla \\alpha]\n$$\n\n对比上面的 `alphaEqn` 减去的 `laplacian` 项，会发现这一项跟那个 `laplacian` 是一样的。\n\n所以，`g0 > 0` 时，先将固相压力带来的通量代入到 `alphaEqn` 的构建中，然后再减去对应的 `laplacian` 项，这么做的目的，应该是为了计算稳定性以及保证 `alpha` 的有界性（保证 $0<alpha<alphaMax$）。但是这背后的数值原理，我也没有完全理解。\n\n\n最后，有必要说明一下，`g0`相关的项其实就是在动量方程中增加了一项颗粒压力，想要达到的效果是防止固相过度堆积。`kineticTheory`开启后将计算颗粒相的应力，其中包括了颗粒相压力。所以 `g0`可以当作 KTGF 的某种简单的替代。从原理上讲，`kineticTheory`和`g0`不应该同时开启。\n\n### 3.3 packingLimiter\npackingLimiter 的作用，从名字可以看出，也是用来处理过度堆积问题的。packingLimiter 缺乏理论基础，仅仅是一种不甚高明的数值技巧，其主要的处理是定义了一个\n```\nvolScalarField alphaEx(max(alpha - alphaMax, scalar(0)));\n```\n当`alpha - alphaMax > 0`时，`alphaEx = alpha - alphaMax > 0`，否则`alphaEx = 0`。\npackingLimiter 本质操作是，当某个网格的固相体积分率超过设定的最大值时，就让该网格的固相往它的邻居网格匀一匀，就是这么简(ren)单(xing)。所以，一般情况下，不建议开启packingLimiter。\n\n至此，OpenFOAM-2.1.1 版本的`twoPhaseEulerFoam`便解读完了。最近的 OpenFOAM-2.3 系列中的`twoPhaseEulerFoam` 变化很大，求解的是全守恒形式的动量方程了（而不是 phase-intensive形式的），耦合了传热模型，考虑了可压缩效应，而且alphaEqn的求解不再是利用隐式迭代的方式，而是改成了用 MULES 方法来求解。这些有待于下一步进行解读。\n\n\n\n## 参考资料\n1.  Henrik Rusche， PHD Thesis， Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase Fractions， Imperial College of Science, Technology & Medicine, Department of Mechanical Engineering, 2002\n2. https://openfoamwiki.net/index.php/BubbleFoam\n","source":"_posts/twoPhaseEulerFoam3.md","raw":"title: \"twoPhaseEulerFoam 全解读之三\"\ndate: 2015-06-27 20:39:01\ncomments: true\ntags:\n  - OpenFOAM\n  - Code Explained\ncategories:\n - OpenFOAM\n---\n\n本系列将对OpenFOAM-2.1.1 中的 `twoPhaseEulerFoam` 求解器进行完全解读，共分三部分：方程推导，代码解读，补充说明。本篇对 `twoPhaseEulerFoam` 中的 `alphaEqn.H` 进行详细地的解读，并作一些补充说明。\n\n<!--more-->\n\n### 2.3. alphaEqn\n前文提到，经过求解 `pEqn`，并修正速度`Ua`和`Ub`以后，总体的连续性便得到了保证。为了得到分散相的体积分率`alpha`，还需要利用得到的速度场来求解分散相的连续性方程，即`alphaEqn`。分散相连续性方程可以表达如下：\n$$\n\\frac{\\partial \\alpha\\_a}{\\partial t}+\\nabla \\cdot (\\alpha\\_a U\\_a)=0\n$$\n\n为了让每一项都写成守恒形式，并且保证$\\alpha_a$的有界性，Weller 将分散相连续性方程写成如下形式\n$$\n\\frac{\\partial \\alpha\\_a}{\\partial t}+\\nabla \\cdot (\\alpha_a U)+\\nabla \\cdot(U\\_r\\alpha\\_a(1-\\alpha\\_a))=0 \n$$\n其中\n$$\nU=\\alpha\\_a U\\_a + \\alpha\\_b U\\_b \\\\\\\\\nU\\_r=U\\_a-U\\_b\n$$\nOpenFOAM-2.1.1 的`alphaEqn`求解的正是 Weller 提出的形式。主要的代码如下：\n```\n fvScalarMatrix alphaEqn\n        (\n             fvm::ddt(alpha)\n           + fvm::div(phic, alpha, scheme)\n           + fvm::div(-fvc::flux(-phir, beta, schemer), alpha, schemer)\n        );\n\n```\n其中`phic`与`phir`的定义如下：\n```\nsurfaceScalarField phic(\"phic\", phi);\nsurfaceScalarField phir(\"phir\", phia - phib);\n\n```\n得到分散相体积分率后，连续相体积分率$\\alpha\\_b$则为$1-\\alpha\\_a$，如下\n```\n beta = scalar(1) - alpha;\n```\n\n## 3. 补充说明\n想必读者肯定发现了，我前面的代码解读相比于`twoPhaseEulerFoam`的源码其实省略了很多，总体上来讲，省略了三大块，一是跟`kineticTheory`相关的，二是跟`g0`相关的，三是`packingLimiter`，下面对这三部分进行一些补充说明。\n### 3.1 kineticTheory\n`kineticTheory`是 KTGF(Kinetic Theory of Granular Flow) 方法在OpenFOAM-2.1.1里的实现。KTGF 的主要作用是计算固相压力和固相粘度，以封闭前述的分散相动量方程，所以`kineticTheory`只有在模拟气固（液固）两相流时才需要开启。 `kineticTheory` 是否开启以及 KTGF 模型的参数需要在算例的`constant/kineticTheoryProperties`文件里进行设置。如果开启了`kineticTheory`，主要的影响如下：\n+ UEqn.H\n```\nif (kineticTheory.on())\n{\n    kineticTheory.solve(gradUaT);\n    nuEffa = kineticTheory.mua()/rhoa;\n}\n\n```\n 如果开启`kineticTheory`，则固相粘度`nuEffa`是由 KTGF 来计算得到，否则`nuEffa`是用一个简单的关联式来计算\n```\nelse\n{\n    nuEffa = sqr(Ct)*nutb + nua;\n}\n\n```\n 此外，`Rca`项也要加上额外的项\n```\nif (kineticTheory.on())\n{\n    Rca -= ((kineticTheory.lambda()/rhoa)*tr(gradUaT))*tensor(I);\n}\n```\n\n+ pEqn.H\n 如果`kineticTheory`开启了，则要在压力修正方程中加上额外的固相压力项。\n```\nif (kineticTheory.on())\n{\n    phiDraga -= rUaAf*fvc::snGrad(kineticTheory.pa()/rhoa)*mesh.magSf();\n}\n```\n\n有关 OpenFOAM 中使用的 KTGF 模型的理论可以参考[Derivation, Implementation, and Validation of Computer Simulation Models for Gas-Solid Fluidized Beds](http://repository.tudelft.nl/view/ir/uuid%3A919e2efa-5db2-40e6-9082-83b1416709a6/)，以及B.G.M. van Wachem 的其他相关论文。\n\n### 3.2 g0\n跟 `kineticTheory`一样，`g0` 也是只有在模拟气固（液固）两相流时才需要开启，相关的设置在`constant/ppProperties`里，当`g0`的值设置为大于零时，则跟`g0`相关的项会其作用，主要的影响如下：\n+ pEqn\n```\nif (g0.value() > 0.0)\n{\n    phiDraga -= ppMagf*fvc::snGrad(alpha)*mesh.magSf();\n}\n```\n 其中`ppMagf`的定义如下：\n ```\n ppMagf = rUaAf*fvc::interpolate\n (\n  (1.0/(rhoa*(alpha + scalar(0.0001))))\n  *g0*min(exp(preAlphaExp*(alpha - alphaMax)), expMax)\n ); \n ```\n 这一段的效果，相当于在[本系列第一篇](http://xiaopingqiu.github.io/2015/05/17/twoPhaseEulerFoam1/)最后的分散相动量方程的等式右边额外增加一项：\n $$\n -g0\\*min(e^{preAlphaExp\\*(\\alpha\\_a - \\alpha\\_{Max})},expMax)\\nabla \\alpha\\_a / (\\alpha\\_a \\rho\\_a)\n $$\n 可见，这一项的作用是给固相额外施加了一个力，可以认为是固相压力，这个力与$\\alpha\\_a$的梯度有关，且与梯度的方向相反。\n注意这一项的特点：`g0`, `preAlphaExp`, `alphaMax`, `expMax` 都是需要用户指定的参数，一般将`g0`, `preAlphaExp`和`expMax`设置为一个比较大的正整数（$10^3$的量级），当`alpha - alphaMax < 0`时，$e^{\\,preAlphaExp\\*(\\alpha\\_a - \\alpha\\_{Max})}$ 将是一个很小的数，此时整个增加的一项也将是一个较小的数，所以，`alpha - alphaMax < 0`时额外增加的那一项对动量方程的贡献很小。但是，当`alpha - alphaMax > 0`时，随着 `alpha` 偏离 `alphaMax` 越来越远，`exp(preAlphaExp*(alpha - alphaMax)`将迅速增大，`min(exp(preAlphaExp*(alpha - alphaMax)), expMax)`的值也将迅速增大，直到设定值`expMax`。可见，`g0`项的作用可以理解为为了防止固相过度堆积。气固系统中，固相的体积分率是有上限的，可以将`alphaMax`设置为这个上限值，当某个网格里的`alpha`超过设定的`alphaMax`时，就让固相迅速弹开，以防止固相体积分率过大。\n\n+ alphaEqn.H\nalphaEqn.H 里有两处跟 `g0` 有关的，\n一处是对 `phic` 和 `phir` 进行修正：\n```\nif (g0.value() > 0.0)\n{\n    surfaceScalarField alphaf(fvc::interpolate(alpha));\n    surfaceScalarField phipp(ppMagf*fvc::snGrad(alpha)*mesh.magSf());\n    phir += phipp;\n    phic += fvc::interpolate(alpha)*phipp;\n}\n```\n另一处是对 `alphaEqn` 进行修正：\n```\nif (g0.value() > 0.0)\n{\n    ppMagf = rUaAf*fvc::interpolate\n    (\n     (1.0/(rhoa*(alpha + scalar(0.0001))))\n     *g0*min(exp(preAlphaExp*(alpha - alphaMax)), expMax)\n     );\n    \n    alphaEqn -= fvm::laplacian\n    (\n     (fvc::interpolate(alpha) + scalar(0.0001))*ppMagf,\n     alpha,\n     \"laplacian(alphaPpMag,alpha)\"\n     );\n}\n```\n为什么当 `g0 > 0` 时，需要在 `alphaEqn` 中额外减去一个 `laplacian` 项呢？这里要结合上面提到的两段代码来进行分析。\n在对 `phic` 和 `phir` 进行修正这一段， `phic` 和 `phir` 分别加上了一项。将修正过的 `phic` 和 `phir` 代入到 `alphaEqn` 中，会导致 `alphaEqn` 与上文的分散相连续性方程\n$$\n\\frac{\\partial \\alpha\\_a}{\\partial t}+\\nabla \\cdot (\\alpha_a U)+\\nabla \\cdot(U\\_r\\alpha\\_a(1-\\alpha\\_a))=0 \n$$\n不一致，其中 `alphaEqn` 多出了几项：\n其中\n` fvm::div(phic, alpha, scheme) ` 比 $ \\nabla \\cdot (\\alpha_a U) $ 多了 ` fvm::div(alphaf*phipp, alpha, scheme) ` 一项，写成公式，就是 $ \\nabla \\cdot [\\alpha (\\alpha *\\mathrm{ppMagf}) \\nabla \\alpha] $ 。\n\n而 ` fvm::div(-fvc::flux(-phir, beta, schemer), alpha, schemer) ` 则比 $ \\nabla \\cdot (\\alpha_a U)+\\nabla \\cdot(U\\_r\\alpha\\_a(1-\\alpha\\_a) $ 多出了 ` fvm::div(-fvc::flux(-phipp, beta, schemer), alpha, schemer) ` 一项，写成公式就是 $ \\nabla \\cdot [\\alpha (\\beta \\*\\mathrm{ppMagf}) \\nabla \\alpha] $ 。\n多出的这两项加起来，为\n$$\n\\nabla \\cdot [\\alpha  *\\mathrm{ppMagf} \\  \\nabla \\alpha]\n$$\n\n对比上面的 `alphaEqn` 减去的 `laplacian` 项，会发现这一项跟那个 `laplacian` 是一样的。\n\n所以，`g0 > 0` 时，先将固相压力带来的通量代入到 `alphaEqn` 的构建中，然后再减去对应的 `laplacian` 项，这么做的目的，应该是为了计算稳定性以及保证 `alpha` 的有界性（保证 $0<alpha<alphaMax$）。但是这背后的数值原理，我也没有完全理解。\n\n\n最后，有必要说明一下，`g0`相关的项其实就是在动量方程中增加了一项颗粒压力，想要达到的效果是防止固相过度堆积。`kineticTheory`开启后将计算颗粒相的应力，其中包括了颗粒相压力。所以 `g0`可以当作 KTGF 的某种简单的替代。从原理上讲，`kineticTheory`和`g0`不应该同时开启。\n\n### 3.3 packingLimiter\npackingLimiter 的作用，从名字可以看出，也是用来处理过度堆积问题的。packingLimiter 缺乏理论基础，仅仅是一种不甚高明的数值技巧，其主要的处理是定义了一个\n```\nvolScalarField alphaEx(max(alpha - alphaMax, scalar(0)));\n```\n当`alpha - alphaMax > 0`时，`alphaEx = alpha - alphaMax > 0`，否则`alphaEx = 0`。\npackingLimiter 本质操作是，当某个网格的固相体积分率超过设定的最大值时，就让该网格的固相往它的邻居网格匀一匀，就是这么简(ren)单(xing)。所以，一般情况下，不建议开启packingLimiter。\n\n至此，OpenFOAM-2.1.1 版本的`twoPhaseEulerFoam`便解读完了。最近的 OpenFOAM-2.3 系列中的`twoPhaseEulerFoam` 变化很大，求解的是全守恒形式的动量方程了（而不是 phase-intensive形式的），耦合了传热模型，考虑了可压缩效应，而且alphaEqn的求解不再是利用隐式迭代的方式，而是改成了用 MULES 方法来求解。这些有待于下一步进行解读。\n\n\n\n## 参考资料\n1.  Henrik Rusche， PHD Thesis， Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase Fractions， Imperial College of Science, Technology & Medicine, Department of Mechanical Engineering, 2002\n2. https://openfoamwiki.net/index.php/BubbleFoam\n","slug":"twoPhaseEulerFoam3","published":1,"updated":"2016-06-01T09:05:25.539Z","_id":"cioiqegba000vz8mb3he945cl","layout":"post","photos":[],"link":""},{"title":"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之曳力模型的调用过程","date":"2015-09-07T14:27:47.000Z","comments":1,"_content":"\n前面有三篇博文对 OpenFOAM-2.1.x 中的 `twoPhaseEulerFoam` 求解器进行了解读，然而 OpenFOAM-2.3.x 中，这个求解器的代码有了很大的变化。本文将以一个曳力模型的调用过程为例，介绍 OpenFOAM-2.3.x 中 `twoPhaseEulerFoam` 是如何调用相间作用力模型的。后续还将对 OpenFOAM-2.3.x 中的 `twoPhaseEulerFoam` 的其他方面进行解读。\n\n<!--more-->\n\n主程序中(\" UEqn.H \")，dragCoeff 定义为：\n```\nvolScalarField dragCoeff(fluid.dragCoeff());\n```\n其中，`fluid` 为 `twoPhaseSystem` 类的对象，所以，要去找  `twoPhaseSystem` 类的成员函数 `dragCoeff()`。\n在源文件 `twoPhaseSystem.C` 中找到如下定义：\n```\nFoam::tmp<Foam::volScalarField> Foam::twoPhaseSystem::dragCoeff() const\n{\n    return drag_->K();\n}\n```\n而  `drag_` 的定义为\n```\nautoPtr<BlendedInterfacialModel<dragModel> > drag_;\n```\n所以，需要找到类 `BlendedInterfacialModel<dragModel>` 的成员函数 `K()`的定义。\n在源文件 `BlendedInterfacialModel.C` 中，找到如下定义：\n```\ntemplate<class modelType>\nFoam::tmp<Foam::volScalarField>\nFoam::BlendedInterfacialModel<modelType>::K() const\n{\n    tmp<volScalarField> f1, f2;\n\n    if (model_.valid() || model1In2_.valid())\n    {\n        f1 = blending_.f1(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n\n    if (model_.valid() || model2In1_.valid())\n    {\n        f2 = blending_.f2(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n\n    tmp<volScalarField> x\n    (\n        new volScalarField\n        (\n            IOobject\n            (\n                modelType::typeName + \"Coeff\",\n                pair_.phase1().mesh().time().timeName(),\n                pair_.phase1().mesh()\n            ),\n            pair_.phase1().mesh(),\n            dimensionedScalar(\"zero\", modelType::dimK, 0)\n        )\n    );\n\n    if (model_.valid())\n    {\n        x() += model_->K()*(f1() - f2());\n    }\n\n    if (model1In2_.valid())\n    {\n        x() += model1In2_->K()*(1 - f1);\n    }\n\n    if (model2In1_.valid())\n    {\n        x() += model2In1_->K()*f2;\n    }\n\n    if (model_.valid() || model1In2_.valid() || model2In1_.valid())\n    {\n        correctFixedFluxBCs(x());\n    }\n\n    return x;\n}\n```\n对于曳力模型，上述成员函数的  `modelType` 可以实例化为 `dragModel`，要理解该函数的行为，有三点需要清楚： `pair_`，  `pair1In2_`， `pair2In1_` 的定义； `model_`， `model1In2_`，  `model2In1_` 的定义； `blendingMethod` 类的成员函数  `f1` 与 `f2` 的定义。下面一一解释：\n\n#### 1.  `pair_`，  `pair1In2_`， `pair2In1_` 的定义\n这三个是 `BlendedInterfacialModel` 类的数据成员，回到 `twoPhaseSystem` 类中去看 `drag_` 的初始化，\n```\n drag_.set\n    (\n        new BlendedInterfacialModel<dragModel>\n        (\n            lookup(\"drag\"),\n            (\n                blendingMethods_.found(\"drag\")\n              ? blendingMethods_[\"drag\"]\n              : blendingMethods_[\"default\"]\n            ),\n            pair_,\n            pair1In2_,\n            pair2In1_\n        )\n    );\n```\n可知，`BlendedInterfacialModel` 类中的 `pair_`，  `pair1In2_`， `pair2In1_` 是将 `twoPhaseSystem` 类的数据成员传递过去来实现初始化的，所以，真正要看懂的是`twoPhaseSystem`类中数据成员`pair_`，  `pair1In2_`， `pair2In1_` 的初始化，见如下代码：\n```\npair_.set\n    (\n        new phasePair\n        (\n            phase1_,\n            phase2_,\n            g,\n            sigmaTable\n        )\n    );\n    pair1In2_.set\n    (\n        new orderedPhasePair\n        (\n            phase1_,\n            phase2_,\n            g,\n            sigmaTable,\n            aspectRatioTable\n        )\n    );\n    pair2In1_.set\n    (\n        new orderedPhasePair\n        (\n            phase2_,\n            phase1_,\n            g,\n            sigmaTable,\n            aspectRatioTable\n        )\n    );\n```\n\n可见，`pair_` 是  `phasePair` 类的指针，  `pair1In2_` 与 `pair2In1_` 是  `orderedPhasePair` 类的指针。\n其中`phase1_` 和  `phase2_` 是通过从文件\"phaseProperties\"里读取内容来初始化的：\n```\nphase1_\n    (\n        *this, \n        *this,\n        wordList(lookup(\"phases\"))[0]\n    ),\n    phase2_\n    (\n        *this,\n        *this,\n        wordList(lookup(\"phases\"))[1]\n    ),\n```\n举例说，假设\"phaseProperties\" 文件里有以下内容：\n```\nphases (particles air);\n```\n则， `phase1_` = \"particles\"， `phase2_` = \"air\" 。\n\n根据 `phasePair` 类中的定义，成员函数 `dispersed()` 总是返回对象的`phase1`（也就是 `phasePair` 或者  `orderedPhasePair ` 类的构造函数的第一个参数），所以，对于 \"particles air\" 体系，`pair1In2_.dispersed() = phase1_.name() = \"particles\"`， 而 `pair2In1_.dispersed() = phase2_.name() = \"air\"`。\n\n#### 2. `model_`， `model1In2_`，  `model2In1_` 的定义\n这三个是 `BlendedInterfacialModel` 类的数据成员，定义和初始化如下： \n```\nautoPtr<modelType> model_;\nautoPtr<modelType> model1In2_;\nautoPtr<modelType> model2In1_;\n\nif (modelTable.found(pair_))\n    {\n        model_.set\n        (\n            modelType::New\n            (\n                modelTable[pair_],\n                pair_\n            ).ptr()\n        );\n    }\n\n    if (modelTable.found(pair1In2_))\n    {\n        model1In2_.set\n        (\n            modelType::New\n            (\n                modelTable[pair1In2_],\n                pair1In2_\n            ).ptr()\n        );\n    }\n\n    if (modelTable.found(pair2In1_))\n    {\n        model2In1_.set\n        (\n            modelType::New\n            (\n                modelTable[pair2In1_],\n                pair2In1_\n            ).ptr()\n        );\n    }\n}\n```\n注意，这里讨论的是曳力模型的调用，所以，如前所述，`modelType` 可以实例化为 `dragModel`。 \n`modelTable` 是 `phasePair::dictTable` 类型的引用，本质上是一个 HashTable（ ` HashTable<dictionary, phasePairKey, phasePairKey::hash>` ），其 key 是 `phasePairKey` 类型的对象，value 是 `dictionary` 类的对象。 `found` 函数通过查找 `modelTable` 对象中是否存在某个 key 来决定返回值是 true 还是 false。\n\n这里要分头说，一边是 `modelTable` 的初始化，另一边是 `pair_`， `pair2In1_`， `pair2In1_`  如何与 `phasePairKey` 类进行对比。\n\n从 `twoPhaseSystem` 类中对 `drag_` 的初始化可知， `modelTable` 的初始化是由 `lookup(\"drag\")` 来完成的。`lookup` 函数的作用是读取\"phaseProperties\" 文件的内容来实现对一个 HashTable 的初始化（具体过程将会在后续解读中涉及）。举例说，以下 \"phaseProperties\" 的内容\n```\ndrag\n(\n    (particles in air)\n    {\n        type            GidaspowErgunWenYu;\n        residualAlpha   1e-6;\n        residualRe      1e-3;\n        swarmCorrection\n        {\n            type        none;\n        }\n    }\n);\n```\n将利用 `(particles in air)` 来初始化一个 `phasePairKey` 对象（利用 `phasePairKey`类中的空白构造函数和重载的 `>>` 符号）。成员`ordeded_` 的值取决于 \"in\" 或 \"and\" ，若形如 \"particles in air \"，`ordeded_ = true`，若形如 \"particles and air \"， 则 `ordeded_ = false`。 则剩余内容将用于初始化一个 `dictionary` 对象。\n\n而另一方面，`phasePair` 是 `phasePairKey`的派生类， `orderedPhasePair ` 则是`phasePair` 的派生类，所以，将 `pair_` ，`pair1In2_` 以及 `pair2In1_` 作为 `found` 函数的参数，隐含了将派生类的引用转换成基类引用。 `phasePair`类默认 `ordered_ = false`， 而 `orderedPhasePair` 类则默认`ordered_ = true`。`pair_` ，`pair1In2_` 以及 `pair2In1_` 与 `modelTable_` 的 key 进行比较，比较的是对应的 `phase1`，`phase2` 和 `ordered_` 三个成员的值是否相等，只有三者都一样时， `found` 函数才返回 `true` 。   所以，对于上面提到的设置，即\n```\nphases (particles air);\ndrag\n(\n    (particles in air)\n    .......\n);\n```\n只有`modelTable.found(pair1In2_)`的值为`true`。同样，也就只有 `model1In2_.valid()` 为 `true` （即 `model1In2_` 指针不为空。）  \n\n#### 3. `blendingMethod` 类的成员函数  `f1` 与 `f2`\n这两个函数的实现在不同的 `blendingMethods`中不一样，以最简单的 `noBlending` 类型为例：\n```\nFoam::tmp<Foam::volScalarField> Foam::blendingMethods::noBlending::f1\n(\n    const phaseModel& phase1,\n    const phaseModel& phase2\n) const\n{\n    const fvMesh& mesh(phase1.mesh());\n\n    return\n        tmp<volScalarField>\n        (\n            new volScalarField\n            (\n                IOobject\n                (\n                    \"f\",\n                    mesh.time().timeName(),\n                    mesh\n                ),\n                mesh,\n                dimensionedScalar\n                (\n                    \"f\",\n                    dimless,\n                    phase2.name() != continuousPhase_\n                ) // 如果 phase2 就是 continuousPhase，那么 f1 = 0；否则 f1 = 1\n            )\n        );\n}\n\nFoam::tmp<Foam::volScalarField> Foam::blendingMethods::noBlending::f2\n(\n    const phaseModel& phase1,\n    const phaseModel& phase2\n) const\n{\n    const fvMesh& mesh(phase1.mesh());\n\n    return\n        tmp<volScalarField>\n        (\n            new volScalarField\n            (\n                IOobject\n                (\n                    \"f\",\n                    mesh.time().timeName(),\n                    mesh\n                ),\n                mesh,\n                dimensionedScalar\n                (\n                    \"f\",\n                    dimless,\n                    phase1.name() == continuousPhase_\n                ) // 如果 phase1 是 continuousPhase，那么 f2 = 1，否则 f2 = 0。\n            )\n        );\n}\n```\n\n再回头看 `BlendedInterfacialModel` 类的成员函数 `K()`，\n```\n if (model_.valid() || model1In2_.valid())\n    {\n        f1 = blending_.f1(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n\n    if (model_.valid() || model2In1_.valid())\n    {\n        f2 = blending_.f2(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n```\n根据上面的 \"phaseProperties\" 的设置，可知 `pair1In2_.dispersed() = \"particles\"`，  `pair2In1_.dispersed() = \"air\" `，而  `continuousPhase_` 是从 \"phaseProperties\" 的 \"blending\" 子字典里读取的，这里`continuousPhase_ = \"air\"`，于是，可以得到 `f1 = 0`， `f2 = 1`。再看 `K()` 的返回值，可知，最终有效的返回值是\n```\n if (model1In2_.valid())\n    {\n        x() += model1In2_->K()*(1 - f1);\n    }\n```\n即，最终 `K()` 函数的返回值是 `model1In2_->K()` 。而`model1In2_->K()`的值就取决于具体调用的曳力模型了，举例说，假如调用的是 Ergun 曳力模型，则 `K()` 最终返回的值，也就是 \"UEqn.H\" 中的 `dragCoeff` 的值是\n```\nFoam::tmp<Foam::volScalarField> Foam::dragModel::K() const\n{\n    return\n        0.75\n       *CdRe()\n       *max(pair_.dispersed(), residualAlpha_)\n       *swarmCorrection_->Cs()\n       *pair_.continuous().rho()\n       *pair_.continuous().nu()\n       /sqr(pair_.dispersed().d());\n}\n```\n其中 `CdRe()` 的定义为\n```\nFoam::tmp<Foam::volScalarField> Foam::dragModels::Ergun::CdRe() const\n{\n    return\n        (4/3)\n       *(\n            150\n           *max(scalar(1) - pair_.continuous(), residualAlpha_)\n           /max(pair_.continuous(), residualAlpha_)\n          + 1.75\n           *pair_.Re()\n        );\n}\n```\n注意所有曳力模型的 `K()`函数形式是一样的，不同曳力模型的区别在于 `CdRe()` 的实现不一样。\n此外，virtualMass，  heatTransfer，lift，wallLubrication，turbulentDispersion 这些子模型的调用也都是经过类似的过程进行的。\n\n","source":"_posts/twoPhaseEulerFoam23x-twoPhaseSystem.md","raw":"title: \"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之曳力模型的调用过程\"\ndate: 2015-09-07 22:27:47\ncomments: true\ntags:\n - OpenFOAM\n - Code Explained \ncategories:\n - OpenFOAM\n---\n\n前面有三篇博文对 OpenFOAM-2.1.x 中的 `twoPhaseEulerFoam` 求解器进行了解读，然而 OpenFOAM-2.3.x 中，这个求解器的代码有了很大的变化。本文将以一个曳力模型的调用过程为例，介绍 OpenFOAM-2.3.x 中 `twoPhaseEulerFoam` 是如何调用相间作用力模型的。后续还将对 OpenFOAM-2.3.x 中的 `twoPhaseEulerFoam` 的其他方面进行解读。\n\n<!--more-->\n\n主程序中(\" UEqn.H \")，dragCoeff 定义为：\n```\nvolScalarField dragCoeff(fluid.dragCoeff());\n```\n其中，`fluid` 为 `twoPhaseSystem` 类的对象，所以，要去找  `twoPhaseSystem` 类的成员函数 `dragCoeff()`。\n在源文件 `twoPhaseSystem.C` 中找到如下定义：\n```\nFoam::tmp<Foam::volScalarField> Foam::twoPhaseSystem::dragCoeff() const\n{\n    return drag_->K();\n}\n```\n而  `drag_` 的定义为\n```\nautoPtr<BlendedInterfacialModel<dragModel> > drag_;\n```\n所以，需要找到类 `BlendedInterfacialModel<dragModel>` 的成员函数 `K()`的定义。\n在源文件 `BlendedInterfacialModel.C` 中，找到如下定义：\n```\ntemplate<class modelType>\nFoam::tmp<Foam::volScalarField>\nFoam::BlendedInterfacialModel<modelType>::K() const\n{\n    tmp<volScalarField> f1, f2;\n\n    if (model_.valid() || model1In2_.valid())\n    {\n        f1 = blending_.f1(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n\n    if (model_.valid() || model2In1_.valid())\n    {\n        f2 = blending_.f2(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n\n    tmp<volScalarField> x\n    (\n        new volScalarField\n        (\n            IOobject\n            (\n                modelType::typeName + \"Coeff\",\n                pair_.phase1().mesh().time().timeName(),\n                pair_.phase1().mesh()\n            ),\n            pair_.phase1().mesh(),\n            dimensionedScalar(\"zero\", modelType::dimK, 0)\n        )\n    );\n\n    if (model_.valid())\n    {\n        x() += model_->K()*(f1() - f2());\n    }\n\n    if (model1In2_.valid())\n    {\n        x() += model1In2_->K()*(1 - f1);\n    }\n\n    if (model2In1_.valid())\n    {\n        x() += model2In1_->K()*f2;\n    }\n\n    if (model_.valid() || model1In2_.valid() || model2In1_.valid())\n    {\n        correctFixedFluxBCs(x());\n    }\n\n    return x;\n}\n```\n对于曳力模型，上述成员函数的  `modelType` 可以实例化为 `dragModel`，要理解该函数的行为，有三点需要清楚： `pair_`，  `pair1In2_`， `pair2In1_` 的定义； `model_`， `model1In2_`，  `model2In1_` 的定义； `blendingMethod` 类的成员函数  `f1` 与 `f2` 的定义。下面一一解释：\n\n#### 1.  `pair_`，  `pair1In2_`， `pair2In1_` 的定义\n这三个是 `BlendedInterfacialModel` 类的数据成员，回到 `twoPhaseSystem` 类中去看 `drag_` 的初始化，\n```\n drag_.set\n    (\n        new BlendedInterfacialModel<dragModel>\n        (\n            lookup(\"drag\"),\n            (\n                blendingMethods_.found(\"drag\")\n              ? blendingMethods_[\"drag\"]\n              : blendingMethods_[\"default\"]\n            ),\n            pair_,\n            pair1In2_,\n            pair2In1_\n        )\n    );\n```\n可知，`BlendedInterfacialModel` 类中的 `pair_`，  `pair1In2_`， `pair2In1_` 是将 `twoPhaseSystem` 类的数据成员传递过去来实现初始化的，所以，真正要看懂的是`twoPhaseSystem`类中数据成员`pair_`，  `pair1In2_`， `pair2In1_` 的初始化，见如下代码：\n```\npair_.set\n    (\n        new phasePair\n        (\n            phase1_,\n            phase2_,\n            g,\n            sigmaTable\n        )\n    );\n    pair1In2_.set\n    (\n        new orderedPhasePair\n        (\n            phase1_,\n            phase2_,\n            g,\n            sigmaTable,\n            aspectRatioTable\n        )\n    );\n    pair2In1_.set\n    (\n        new orderedPhasePair\n        (\n            phase2_,\n            phase1_,\n            g,\n            sigmaTable,\n            aspectRatioTable\n        )\n    );\n```\n\n可见，`pair_` 是  `phasePair` 类的指针，  `pair1In2_` 与 `pair2In1_` 是  `orderedPhasePair` 类的指针。\n其中`phase1_` 和  `phase2_` 是通过从文件\"phaseProperties\"里读取内容来初始化的：\n```\nphase1_\n    (\n        *this, \n        *this,\n        wordList(lookup(\"phases\"))[0]\n    ),\n    phase2_\n    (\n        *this,\n        *this,\n        wordList(lookup(\"phases\"))[1]\n    ),\n```\n举例说，假设\"phaseProperties\" 文件里有以下内容：\n```\nphases (particles air);\n```\n则， `phase1_` = \"particles\"， `phase2_` = \"air\" 。\n\n根据 `phasePair` 类中的定义，成员函数 `dispersed()` 总是返回对象的`phase1`（也就是 `phasePair` 或者  `orderedPhasePair ` 类的构造函数的第一个参数），所以，对于 \"particles air\" 体系，`pair1In2_.dispersed() = phase1_.name() = \"particles\"`， 而 `pair2In1_.dispersed() = phase2_.name() = \"air\"`。\n\n#### 2. `model_`， `model1In2_`，  `model2In1_` 的定义\n这三个是 `BlendedInterfacialModel` 类的数据成员，定义和初始化如下： \n```\nautoPtr<modelType> model_;\nautoPtr<modelType> model1In2_;\nautoPtr<modelType> model2In1_;\n\nif (modelTable.found(pair_))\n    {\n        model_.set\n        (\n            modelType::New\n            (\n                modelTable[pair_],\n                pair_\n            ).ptr()\n        );\n    }\n\n    if (modelTable.found(pair1In2_))\n    {\n        model1In2_.set\n        (\n            modelType::New\n            (\n                modelTable[pair1In2_],\n                pair1In2_\n            ).ptr()\n        );\n    }\n\n    if (modelTable.found(pair2In1_))\n    {\n        model2In1_.set\n        (\n            modelType::New\n            (\n                modelTable[pair2In1_],\n                pair2In1_\n            ).ptr()\n        );\n    }\n}\n```\n注意，这里讨论的是曳力模型的调用，所以，如前所述，`modelType` 可以实例化为 `dragModel`。 \n`modelTable` 是 `phasePair::dictTable` 类型的引用，本质上是一个 HashTable（ ` HashTable<dictionary, phasePairKey, phasePairKey::hash>` ），其 key 是 `phasePairKey` 类型的对象，value 是 `dictionary` 类的对象。 `found` 函数通过查找 `modelTable` 对象中是否存在某个 key 来决定返回值是 true 还是 false。\n\n这里要分头说，一边是 `modelTable` 的初始化，另一边是 `pair_`， `pair2In1_`， `pair2In1_`  如何与 `phasePairKey` 类进行对比。\n\n从 `twoPhaseSystem` 类中对 `drag_` 的初始化可知， `modelTable` 的初始化是由 `lookup(\"drag\")` 来完成的。`lookup` 函数的作用是读取\"phaseProperties\" 文件的内容来实现对一个 HashTable 的初始化（具体过程将会在后续解读中涉及）。举例说，以下 \"phaseProperties\" 的内容\n```\ndrag\n(\n    (particles in air)\n    {\n        type            GidaspowErgunWenYu;\n        residualAlpha   1e-6;\n        residualRe      1e-3;\n        swarmCorrection\n        {\n            type        none;\n        }\n    }\n);\n```\n将利用 `(particles in air)` 来初始化一个 `phasePairKey` 对象（利用 `phasePairKey`类中的空白构造函数和重载的 `>>` 符号）。成员`ordeded_` 的值取决于 \"in\" 或 \"and\" ，若形如 \"particles in air \"，`ordeded_ = true`，若形如 \"particles and air \"， 则 `ordeded_ = false`。 则剩余内容将用于初始化一个 `dictionary` 对象。\n\n而另一方面，`phasePair` 是 `phasePairKey`的派生类， `orderedPhasePair ` 则是`phasePair` 的派生类，所以，将 `pair_` ，`pair1In2_` 以及 `pair2In1_` 作为 `found` 函数的参数，隐含了将派生类的引用转换成基类引用。 `phasePair`类默认 `ordered_ = false`， 而 `orderedPhasePair` 类则默认`ordered_ = true`。`pair_` ，`pair1In2_` 以及 `pair2In1_` 与 `modelTable_` 的 key 进行比较，比较的是对应的 `phase1`，`phase2` 和 `ordered_` 三个成员的值是否相等，只有三者都一样时， `found` 函数才返回 `true` 。   所以，对于上面提到的设置，即\n```\nphases (particles air);\ndrag\n(\n    (particles in air)\n    .......\n);\n```\n只有`modelTable.found(pair1In2_)`的值为`true`。同样，也就只有 `model1In2_.valid()` 为 `true` （即 `model1In2_` 指针不为空。）  \n\n#### 3. `blendingMethod` 类的成员函数  `f1` 与 `f2`\n这两个函数的实现在不同的 `blendingMethods`中不一样，以最简单的 `noBlending` 类型为例：\n```\nFoam::tmp<Foam::volScalarField> Foam::blendingMethods::noBlending::f1\n(\n    const phaseModel& phase1,\n    const phaseModel& phase2\n) const\n{\n    const fvMesh& mesh(phase1.mesh());\n\n    return\n        tmp<volScalarField>\n        (\n            new volScalarField\n            (\n                IOobject\n                (\n                    \"f\",\n                    mesh.time().timeName(),\n                    mesh\n                ),\n                mesh,\n                dimensionedScalar\n                (\n                    \"f\",\n                    dimless,\n                    phase2.name() != continuousPhase_\n                ) // 如果 phase2 就是 continuousPhase，那么 f1 = 0；否则 f1 = 1\n            )\n        );\n}\n\nFoam::tmp<Foam::volScalarField> Foam::blendingMethods::noBlending::f2\n(\n    const phaseModel& phase1,\n    const phaseModel& phase2\n) const\n{\n    const fvMesh& mesh(phase1.mesh());\n\n    return\n        tmp<volScalarField>\n        (\n            new volScalarField\n            (\n                IOobject\n                (\n                    \"f\",\n                    mesh.time().timeName(),\n                    mesh\n                ),\n                mesh,\n                dimensionedScalar\n                (\n                    \"f\",\n                    dimless,\n                    phase1.name() == continuousPhase_\n                ) // 如果 phase1 是 continuousPhase，那么 f2 = 1，否则 f2 = 0。\n            )\n        );\n}\n```\n\n再回头看 `BlendedInterfacialModel` 类的成员函数 `K()`，\n```\n if (model_.valid() || model1In2_.valid())\n    {\n        f1 = blending_.f1(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n\n    if (model_.valid() || model2In1_.valid())\n    {\n        f2 = blending_.f2(pair1In2_.dispersed(), pair2In1_.dispersed());\n    }\n```\n根据上面的 \"phaseProperties\" 的设置，可知 `pair1In2_.dispersed() = \"particles\"`，  `pair2In1_.dispersed() = \"air\" `，而  `continuousPhase_` 是从 \"phaseProperties\" 的 \"blending\" 子字典里读取的，这里`continuousPhase_ = \"air\"`，于是，可以得到 `f1 = 0`， `f2 = 1`。再看 `K()` 的返回值，可知，最终有效的返回值是\n```\n if (model1In2_.valid())\n    {\n        x() += model1In2_->K()*(1 - f1);\n    }\n```\n即，最终 `K()` 函数的返回值是 `model1In2_->K()` 。而`model1In2_->K()`的值就取决于具体调用的曳力模型了，举例说，假如调用的是 Ergun 曳力模型，则 `K()` 最终返回的值，也就是 \"UEqn.H\" 中的 `dragCoeff` 的值是\n```\nFoam::tmp<Foam::volScalarField> Foam::dragModel::K() const\n{\n    return\n        0.75\n       *CdRe()\n       *max(pair_.dispersed(), residualAlpha_)\n       *swarmCorrection_->Cs()\n       *pair_.continuous().rho()\n       *pair_.continuous().nu()\n       /sqr(pair_.dispersed().d());\n}\n```\n其中 `CdRe()` 的定义为\n```\nFoam::tmp<Foam::volScalarField> Foam::dragModels::Ergun::CdRe() const\n{\n    return\n        (4/3)\n       *(\n            150\n           *max(scalar(1) - pair_.continuous(), residualAlpha_)\n           /max(pair_.continuous(), residualAlpha_)\n          + 1.75\n           *pair_.Re()\n        );\n}\n```\n注意所有曳力模型的 `K()`函数形式是一样的，不同曳力模型的区别在于 `CdRe()` 的实现不一样。\n此外，virtualMass，  heatTransfer，lift，wallLubrication，turbulentDispersion 这些子模型的调用也都是经过类似的过程进行的。\n\n","slug":"twoPhaseEulerFoam23x-twoPhaseSystem","published":1,"updated":"2015-09-25T09:06:26.746Z","layout":"post","photos":[],"link":"","_id":"cioiqegbh000zz8mbjsizb3x4"},{"title":"twoPhaseEulerFoam 全解读之二","date":"2015-05-17T07:23:04.000Z","comments":1,"_content":"\n本系列将对OpenFOAM-2.1.1 中的 `twoPhaseEulerFoam` 求解器进行完全解读，共分三部分：方程推导，代码解读，补充说明。本篇对 `twoPhaseEulerFoam` 中的 `UEqn.H` 和 `pEqn.H` 中的代码进行详细地的解读。\n\n<!--more-->\n\n## 2. 代码解读\n### 2.1. UEqn\n前一篇导出了分散相的动量守恒方程\n$$\n\\begin{aligned}\n&(1+\\frac{\\alpha\\_b \\rho\\_b}{\\rho\\_a} C\\_{vm})(\\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot \\nabla U\\_a ) -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a - \\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\} -\\frac{\\nabla p}{\\rho\\_a} + g + \\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b \n \\end{aligned}\n$$\n这一篇分析`twoPhaseEulerFoam`求解器是怎么来对动量方程进行离散的，以及，如果通过构建压力方程来对速度进行修正以保证两相的连续性。\n\n注意上述动量方程中，有两项还需要处理一下：\n$$U\\_a \\cdot \\nabla U\\_a=\\nabla\\cdot(U\\_aU\\_a)-U\\_a(\\nabla\\cdot U\\_a)$$\n\n$$\\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a}\\cdot\\left[-\\nu\\_{eff} \\nabla U\\_a\\right] = \\nabla \\cdot\\left[ -\\nu\\_{eff}\\frac{(\\nabla \\alpha\\_a)}{\\alpha\\_a}(\\nabla U\\_a)\\right]-U\\_a\\left(\\nabla\\cdot(-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}) \\right)$$\n转化前后的形式从数学上来等价的，但是在有限体积离散过程中，转化前的 $U\\_a \\cdot \\nabla U\\_a$  和 $\\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a}\\cdot\\left[-\\nu\\_{eff} \\nabla U\\_a\\right]$ 对于$U\\_a$来说是非守恒的，转化后的形式是守恒的(参考[这篇文献](http://www.sciencedirect.com/science/article/pii/S0029549309003021)，注意这样转化后，动量方程空间上是守恒的，但时间上仍是不守恒的，[这个帖子](http://www.cfd-online.com/Forums/openfoam-solving/71141-rewriting-twophaseeulerfoam-conservative-form.html)也有一些有价值的信息)。\n\n这样转化以后，得到的动量方程就跟`twoPhaseEulerFoam`里的定义是一模一样了:\n$$\n\\begin{aligned}\n&(1+\\frac{\\alpha\\_b \\rho\\_b}{\\rho\\_a} C\\_{vm})\\left(\\frac{\\partial U\\_a}{\\partial t} + \\nabla\\cdot(U\\_aU\\_a)-U\\_a(\\nabla\\cdot U\\_a) \\right)\\\\\\\\\n\\-&\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] +\\nabla \\cdot\\left[ -\\nu\\_{eff}\\frac{(\\nabla \\alpha\\_a)}{\\alpha\\_a}(\\nabla U\\_a)\\right]-U\\_a\\left(\\nabla\\cdot(-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}) \\right) \\\\\\\\\n\\+ & \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a - \\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\} \\\\\\\\\n\\-&\\frac{\\nabla p}{\\rho\\_a} + g + \\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b \n \\end{aligned}\n$$\n\n下面将动量方程的每一项与`twoPhaseEulerFoam`的`UEqn.H`的代码一一对应。\n```\n(scalar(1) + Cvm*rhob*beta/rhoa)*\n            (\n                fvm::ddt(Ua)\n              + fvm::div(phia, Ua, \"div(phia,Ua)\")\n              - fvm::Sp(fvc::div(phia), Ua)\n            )\n\n```\n`fvm::ddt(Ua)`对应$\\frac{\\partial U\\_a}{\\partial t}$，`phia`定义为`fvc::interpolate(Ua) & mesh.Sf()`，于是`fvm::div(phia, Ua, \"div(phia,Ua)\")` 和 `fvm::Sp(fvc::div(phia), Ua)` 便分别对应 $\\nabla\\cdot(U\\_aU\\_a)$ 和 $U\\_a(\\nabla\\cdot U\\_a)$了 **[ *注一* ]**。\n\n```\n - fvm::laplacian(nuEffa, Ua)\n + fvc::div(Rca)\n + fvm::div(phiRa, Ua, \"div(phia,Ua)\")\n - fvm::Sp(fvc::div(phiRa), Ua)\n\n```\n`fvm::laplacian(nuEffa, Ua)`对应$\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ]$，`fvc::div(Rca)`对应$\\nabla \\cdot \\left[ R\\_{c,a}\\right]$。\n`phiRa`的定义是\n```\n-fvc::interpolate(nuEffa)*mesh.magSf()*fvc::snGrad(alpha)\n            /fvc::interpolate(alpha + scalar(0.001))\n```\n相当于$-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ **[ *注二* ]**。\n于是 `fvm::div(phiRa, Ua, \"div(phia,Ua)\")` 和 `fvm::Sp(fvc::div(phiRa), Ua)` 便分别对应 $\\nabla \\cdot\\left[ -\\nu\\_{eff} \\frac{(\\nabla \\alpha\\_a)}{\\alpha\\_a}(\\nabla U\\_a)\\right] $ 和 $U\\_a\\left(\\nabla\\cdot(-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}) \\right)$。\n\n```\n(fvc::grad(alpha)/(fvc::average(alpha) + scalar(0.001)) & Rca)\n```\n对应$\\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ R\\_{c,a}\\right]$，其中`&`运算符已重载为计算矢量与张量的点乘积 **[ *注三* ]**。\n\n```\n ==\n//  g                          // Buoyancy term transfered to p-equation\n- fvm::Sp(beta/rhoa*K, Ua)\n//+ beta/rhoa*K*Ub             // Explicit drag transfered to p-equation\n- beta/rhoa*(liftCoeff - Cvm*rhob*DDtUb)\n        );\n```\n`fvm::Sp(beta/rhoa*K, Ua)`对应 $\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a$，`beta/rhoa*(liftCoeff - Cvm*rhob*DDtUb)` 对应 \n$$\n\\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\}\n$$\n其中变量`liftCoeff`定义为\n```\nvolVectorField liftCoeff(Cl*(beta*rhob + alpha*rhoa)*(Ur ^ fvc::curl(U)));\n```\n`DDtUb`定义为\n```\nDDtUb =\n        fvc::ddt(Ub)\n      + fvc::div(phib, Ub)\n      - fvc::div(phib)*Ub;\n\n```\n\n重力 $g$ 以及曳力的显式项 $\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b $ 如注释所述，将会在 `pEqn` 中考虑，压力梯度项 $\\frac{\\nabla p}{\\rho\\_a}$ 则将在 `pEqn` 中用来约束两相的连续性。\n至此动量方程的每一项都与`UEqn.H`的代码对应起来了。\n\n### 2.2. pEqn\n压力方程的作用是修正两相速度$U\\_a$ 和 $U\\_b$以使速度满足连续性方程。将两相的连续性方程加起来，得到总体的连续性方程如下 **[ *注四* ]**：\n$$\\begin{aligned}\n& \\frac{\\partial \\alpha\\_a}{\\partial t} + \\nabla \\cdot (\\alpha\\_a U\\_a) + \\frac{\\partial \\alpha\\_b}{\\partial t} + \\nabla \\cdot (\\alpha\\_b U\\_b) \\\\\\\\\n= & \\frac{\\partial (\\alpha\\_a+\\alpha\\_b)}{\\partial t} + \\nabla \\cdot (\\alpha\\_a U\\_a+\\alpha\\_b U\\_b) = 0\n\\end{aligned}$$\n由于$\\alpha\\_a+\\alpha\\_b=1$，于是两相连续性方程等价于\n$$\n\\nabla \\cdot (\\alpha\\_a U\\_a+\\alpha\\_b U\\_b) = 0\n$$\n\n再来看压力方程是如何构建起来的。\n完整的动量方程离散后，可以写作如下的统一形式：\n$$\na\\_{p,a}U\\_{p,a}=H(U\\_a)-\\frac{\\nabla p}{\\rho\\_a}+\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b +g\n$$\n\n$$\na\\_{p,b}U\\_{p,b}=H(U\\_b)-\\frac{\\nabla p}{\\rho\\_b}+\\frac{\\alpha\\_a}{\\rho\\_b} K U\\_a +g\n$$\n其中$H(U\\_a)$ 和 $H(U\\_b)$ 包含了动量方程中除 压力梯度项，显式曳力项以及重力项以后所有项的贡献。\n由此离散方程可以得到 $U\\_a$ 和 $U\\_b$ 的表达式如下：\n$$\nU\\_{a}=\\frac{1}{a\\_{p,a}}H(U\\_a)-\\frac{\\nabla p}{a\\_{p,a}\\rho\\_a}+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g\n$$\n\n$$\nU\\_{b}=\\frac{1}{a\\_{p,b}}H(U\\_b)-\\frac{\\nabla p}{a\\_{p,b}\\rho\\_b}+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g\n$$\n\n如果此 $U\\_a$ 和 $U\\_b$ 是方程组的解，那么它们必须满足整体的连续性方程，即\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_a (\\frac{1}{a\\_{p,a}}H(U\\_a)-\\frac{\\nabla p}{a\\_{p,a}\\rho\\_a}+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g)\\right] \\\\\\\\ \n\\+ & \\nabla \\cdot \\left[ \\alpha\\_b (\\frac{1}{a\\_{p,b}}H(U\\_b)-\\frac{\\nabla p}{a\\_{p,b}\\rho\\_b}+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g ) \\right] = 0\n\\end{aligned}$$\n将压力梯度项移到方程的一边，得到\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_a (\\frac{1}{a\\_{p,a}}H(U\\_a)+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g)\\right] + \\nabla \\cdot \\left[ \\alpha\\_b (\\frac{1}{a\\_{p,b}}H(U\\_b)+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g ) \\right] \\\\\\\\\n= &\\nabla \\cdot \\left[ (\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})  \\nabla p \\right ]\n\\end{aligned}$$\n**这便是压力修正方程的原型**。\n在`pEqn.H`中，压力方程其实修正的是界面通量，压力方程迭代收敛以后能保证界面通量的连续性。所以，散度表达式需要根据高斯定理写成界面通量之和的形式：\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_a (\\frac{1}{a\\_{p,a}}H(U\\_a)+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g)\\right] \\\\\\\\\n= & (\\alpha\\_a)\\_f \\left[\\sum\\_f(\\frac{1}{a\\_{p,a}})\\_f H(U\\_a)\\cdot S\\_f + \\sum\\_f(\\frac{\\alpha\\_b K}{ a\\_{p,a} \\rho\\_a})\\_f U\\_b \\cdot S\\_f +\\sum\\_f(\\frac{1}{a\\_{p,a}})\\_f g \\cdot S\\_f \\right ]\n\\end{aligned}$$\n下标 $\\_f$ 表示该项将要在代码中用界面上的变量来表示，在OpenFOAM中，即`surfaceScalarField`，$S\\_f$ 表示界面的面积矢量，下面的公式里也是一样。\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_b (\\frac{1}{a\\_{p,b}}H(U\\_b)+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g)\\right] \\\\\\\\\n= & (\\alpha\\_b)\\_f \\left[\\sum\\_f(\\frac{1}{a\\_{p,b}})\\_f H(U\\_b)\\cdot S\\_f + \\sum\\_f(\\frac{\\alpha\\_a K}{ a\\_{p,b} \\rho\\_b})\\_f U\\_a \\cdot S\\_f +\\sum\\_f(\\frac{1}{a\\_{p,b}})\\_f g \\cdot S\\_f \\right]\n\\end{aligned}$$\n\n以及\n$$\n \\nabla \\cdot \\left[ (\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})  \\nabla p \\right ] = \\sum\\_f (\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})\\_f (\\nabla p) \\cdot S\\_f\n$$\n下面是`pEqn`定义了几个跟界面通量有关的变量：\n```\n    surfaceScalarField alphaf(fvc::interpolate(alpha));\n    surfaceScalarField betaf(scalar(1) - alphaf);\n\n    volScalarField rUaA(1.0/UaEqn.A());\n    volScalarField rUbA(1.0/UbEqn.A());\n\n    phia == (fvc::interpolate(Ua) & mesh.Sf());\n    phib == (fvc::interpolate(Ub) & mesh.Sf());\n\n    rUaAf = fvc::interpolate(rUaA);\n    surfaceScalarField rUbAf(fvc::interpolate(rUbA));\n\n    Ua = rUaA*UaEqn.H();\n    Ub = rUbA*UbEqn.H();\n\n    surfaceScalarField phiDraga\n    (\n        fvc::interpolate(beta/rhoa*K*rUaA)*phib + rUaAf*(g & mesh.Sf())\n    );\n\n    \n    surfaceScalarField phiDragb\n    (\n        fvc::interpolate(alpha/rhob*K*rUbA)*phia + rUbAf*(g & mesh.Sf())\n    );\n\n\n    phia = (fvc::interpolate(Ua) & mesh.Sf()) + fvc::ddtPhiCorr(rUaA, Ua, phia) + phiDraga;\n    phib = (fvc::interpolate(Ub) & mesh.Sf()) + fvc::ddtPhiCorr(rUbA, Ub, phib) + phiDragb;\n\n    phi = alphaf*phia + betaf*phib;\n\n    surfaceScalarField Dp\n    (\n        \"(rho*(1|A(U)))\",\n        alphaf*rUaAf/rhoa + betaf*rUbAf/rhob\n    );\n\n```\n`phiDraga` 和 `phiDragb` 分别对应 $(\\frac{\\alpha\\_b K}{ a\\_{p,a} \\rho\\_a})\\_f U\\_b \\cdot S\\_f +(\\frac{1}{a\\_{p,a}})\\_f g \\cdot S\\_f$ 和 $(\\frac{\\alpha\\_a K}{ a\\_{p,b} \\rho\\_b})\\_f U\\_a \\cdot S\\_f +(\\frac{1}{a\\_{p,b}})\\_f g \\cdot S\\_f$\n\n由于13-14行的定义，28-29行中的 `(fvc::interpolate(Ua) & mesh.Sf())` 和 `(fvc::interpolate(Ub) & mesh.Sf())` 便分别对应的是 $(\\frac{1}{a\\_{p,a}})\\_f H(U\\_a)\\cdot S\\_f$ 和 $(\\frac{1}{a\\_{p,b}})\\_f H(U\\_b)\\cdot S\\_f$ 。\n\n有了上面的定义，可以看出31行定义的`phi=alphaf*phia + betaf*phib`便表示了压力方程的左边。\n\n再看33-36行定义的`Dp`，很显然，表示的是压力方程右边的$(\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})\\_f$。\n\n有了以上的定义，便可以构建用于修正界面通量的压力方程了：\n```\nfvScalarMatrix pEqn\n  (\n     fvm::laplacian(Dp, p) == fvc::div(phi)\n  );\n\n```\n如上所述，`pEqn`收敛以后，得到的就是满足连续性的界面通量了，然后再利用求得的界面通量来修正两相的速度，便得到了满足两相连续性的速度：\n```\nUa += fvc::reconstruct(phiDraga - rUaAf*SfGradp/rhoa);\nUa.correctBoundaryConditions();\n\nUb += fvc::reconstruct(phiDragb - rUbAf*SfGradp/rhob);\nUb.correctBoundaryConditions();\n\nU = alpha*Ua + beta*Ub;\n```\n注意，`Ua`和`Ub`为什么是这样来修正呢？回想上面变量定义那个代码段的13-14行，这两行将`Ua`和`Ub`分别定义成了$\\frac{1}{a\\_{p,a}} H(U\\_a)$ 和 $\\frac{1}{a\\_{p,b}} H(U\\_b)$。\n回想`Ua`和`Ub`的离散方程的统一形式\n$$\nU\\_{a}=\\frac{1}{a\\_{p,a}}H(U\\_a)-\\frac{\\nabla p}{a\\_{p,a}\\rho\\_a}+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g\n$$\n\n$$\nU\\_{b}=\\frac{1}{a\\_{p,b}}H(U\\_b)-\\frac{\\nabla p}{a\\_{p,b}\\rho\\_b}+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g\n$$\n\n会发现13-14行定义的`Ua`和`Ub`都少了几项，所以缺了的这几项的贡献需要在速度修正步骤加回来，而`Ua+=`后面的`fvc::reconstruct(phiDraga - rUaAf*SfGradp/rhoa)`刚好就对应着`Ua`缺少的那几项。因为经过压力方程修正以后，界面通量是连续的，所以，将缺失的几项对应的界面通量通过`reconstruct`函数从界面通量重构从对体中心的速度的贡献，便得到了满足连续性的体中心速度了。对`Ub`也是同样的。\n\n经过以上步骤，便能得到满足整体连续性的两相速度`Ua` 和 `Ub`了。\n\n\n## 注释\n**注一**：OpenFOAM 里的`div`函数，字面意义上看起来好像是散度的意思，实际上，`div`函数执行的是**加和**运算。举例说，对于`fvc::div(phia)`，`phia`是`surfaceScalarField`，其值为`(fvc::interpolate(Ua) & mesh.Sf())`，即将存储在体中心的`Ua`插值到每个网格对应的面的面心，然后用面心的速度与该面的面积矢量点乘。从代码中看，`fvc::div(phia)`对应的是 $\\nabla \\cdot U\\_a$，根据高斯定理，也就是 $\\sum\\_f (U\\_a)\\_f \\cdot S\\_f$，而`phia`对应着 $(U\\_a)\\_f \\cdot S\\_f$，所以，`fvc::div(phia)`实际进行的运算是将包围每个网格的面上的通量加起来。更详细的说明见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)。\n**注二**：注意这里的$\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ 在代码中的表示方法，详细说明见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)。\n**注三**：注意这里的$\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ 在代码中的表示方法，以及与上一个$\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ 的区别，详细说明见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)。\n**注四**：这里说的总体的连续性方程指的是**总体体积的守恒**，而不是总体质量的守恒，这二者的差异见Henrik Rusche 的 PHD 论文 P112 的说明。\n\n## 参考资料\n1.  Henrik Rusche， PHD Thesis， Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase Fractions， Imperial College of Science, Technology & Medicine, Department of Mechanical Engineering, 2002\n2. https://openfoamwiki.net/index.php/BubbleFoam\n3. http://www.cfd-online.com/Forums/openfoam-solving/71141-rewriting-twophaseeulerfoam-conservative-form.html\n\n","source":"_posts/twoPhaseEulerFoam2.md","raw":"title: \"twoPhaseEulerFoam 全解读之二\"\ndate: 2015-05-17 15:23:04\ncomments: true\ntags:\n  - OpenFOAM\n  - Code Explained\ncategories:\n - OpenFOAM\n---\n\n本系列将对OpenFOAM-2.1.1 中的 `twoPhaseEulerFoam` 求解器进行完全解读，共分三部分：方程推导，代码解读，补充说明。本篇对 `twoPhaseEulerFoam` 中的 `UEqn.H` 和 `pEqn.H` 中的代码进行详细地的解读。\n\n<!--more-->\n\n## 2. 代码解读\n### 2.1. UEqn\n前一篇导出了分散相的动量守恒方程\n$$\n\\begin{aligned}\n&(1+\\frac{\\alpha\\_b \\rho\\_b}{\\rho\\_a} C\\_{vm})(\\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot \\nabla U\\_a ) -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a - \\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\} -\\frac{\\nabla p}{\\rho\\_a} + g + \\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b \n \\end{aligned}\n$$\n这一篇分析`twoPhaseEulerFoam`求解器是怎么来对动量方程进行离散的，以及，如果通过构建压力方程来对速度进行修正以保证两相的连续性。\n\n注意上述动量方程中，有两项还需要处理一下：\n$$U\\_a \\cdot \\nabla U\\_a=\\nabla\\cdot(U\\_aU\\_a)-U\\_a(\\nabla\\cdot U\\_a)$$\n\n$$\\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a}\\cdot\\left[-\\nu\\_{eff} \\nabla U\\_a\\right] = \\nabla \\cdot\\left[ -\\nu\\_{eff}\\frac{(\\nabla \\alpha\\_a)}{\\alpha\\_a}(\\nabla U\\_a)\\right]-U\\_a\\left(\\nabla\\cdot(-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}) \\right)$$\n转化前后的形式从数学上来等价的，但是在有限体积离散过程中，转化前的 $U\\_a \\cdot \\nabla U\\_a$  和 $\\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a}\\cdot\\left[-\\nu\\_{eff} \\nabla U\\_a\\right]$ 对于$U\\_a$来说是非守恒的，转化后的形式是守恒的(参考[这篇文献](http://www.sciencedirect.com/science/article/pii/S0029549309003021)，注意这样转化后，动量方程空间上是守恒的，但时间上仍是不守恒的，[这个帖子](http://www.cfd-online.com/Forums/openfoam-solving/71141-rewriting-twophaseeulerfoam-conservative-form.html)也有一些有价值的信息)。\n\n这样转化以后，得到的动量方程就跟`twoPhaseEulerFoam`里的定义是一模一样了:\n$$\n\\begin{aligned}\n&(1+\\frac{\\alpha\\_b \\rho\\_b}{\\rho\\_a} C\\_{vm})\\left(\\frac{\\partial U\\_a}{\\partial t} + \\nabla\\cdot(U\\_aU\\_a)-U\\_a(\\nabla\\cdot U\\_a) \\right)\\\\\\\\\n\\-&\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] +\\nabla \\cdot\\left[ -\\nu\\_{eff}\\frac{(\\nabla \\alpha\\_a)}{\\alpha\\_a}(\\nabla U\\_a)\\right]-U\\_a\\left(\\nabla\\cdot(-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}) \\right) \\\\\\\\\n\\+ & \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a - \\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\} \\\\\\\\\n\\-&\\frac{\\nabla p}{\\rho\\_a} + g + \\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b \n \\end{aligned}\n$$\n\n下面将动量方程的每一项与`twoPhaseEulerFoam`的`UEqn.H`的代码一一对应。\n```\n(scalar(1) + Cvm*rhob*beta/rhoa)*\n            (\n                fvm::ddt(Ua)\n              + fvm::div(phia, Ua, \"div(phia,Ua)\")\n              - fvm::Sp(fvc::div(phia), Ua)\n            )\n\n```\n`fvm::ddt(Ua)`对应$\\frac{\\partial U\\_a}{\\partial t}$，`phia`定义为`fvc::interpolate(Ua) & mesh.Sf()`，于是`fvm::div(phia, Ua, \"div(phia,Ua)\")` 和 `fvm::Sp(fvc::div(phia), Ua)` 便分别对应 $\\nabla\\cdot(U\\_aU\\_a)$ 和 $U\\_a(\\nabla\\cdot U\\_a)$了 **[ *注一* ]**。\n\n```\n - fvm::laplacian(nuEffa, Ua)\n + fvc::div(Rca)\n + fvm::div(phiRa, Ua, \"div(phia,Ua)\")\n - fvm::Sp(fvc::div(phiRa), Ua)\n\n```\n`fvm::laplacian(nuEffa, Ua)`对应$\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ]$，`fvc::div(Rca)`对应$\\nabla \\cdot \\left[ R\\_{c,a}\\right]$。\n`phiRa`的定义是\n```\n-fvc::interpolate(nuEffa)*mesh.magSf()*fvc::snGrad(alpha)\n            /fvc::interpolate(alpha + scalar(0.001))\n```\n相当于$-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ **[ *注二* ]**。\n于是 `fvm::div(phiRa, Ua, \"div(phia,Ua)\")` 和 `fvm::Sp(fvc::div(phiRa), Ua)` 便分别对应 $\\nabla \\cdot\\left[ -\\nu\\_{eff} \\frac{(\\nabla \\alpha\\_a)}{\\alpha\\_a}(\\nabla U\\_a)\\right] $ 和 $U\\_a\\left(\\nabla\\cdot(-\\nu\\_{eff}\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}) \\right)$。\n\n```\n(fvc::grad(alpha)/(fvc::average(alpha) + scalar(0.001)) & Rca)\n```\n对应$\\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ R\\_{c,a}\\right]$，其中`&`运算符已重载为计算矢量与张量的点乘积 **[ *注三* ]**。\n\n```\n ==\n//  g                          // Buoyancy term transfered to p-equation\n- fvm::Sp(beta/rhoa*K, Ua)\n//+ beta/rhoa*K*Ub             // Explicit drag transfered to p-equation\n- beta/rhoa*(liftCoeff - Cvm*rhob*DDtUb)\n        );\n```\n`fvm::Sp(beta/rhoa*K, Ua)`对应 $\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a$，`beta/rhoa*(liftCoeff - Cvm*rhob*DDtUb)` 对应 \n$$\n\\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\}\n$$\n其中变量`liftCoeff`定义为\n```\nvolVectorField liftCoeff(Cl*(beta*rhob + alpha*rhoa)*(Ur ^ fvc::curl(U)));\n```\n`DDtUb`定义为\n```\nDDtUb =\n        fvc::ddt(Ub)\n      + fvc::div(phib, Ub)\n      - fvc::div(phib)*Ub;\n\n```\n\n重力 $g$ 以及曳力的显式项 $\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b $ 如注释所述，将会在 `pEqn` 中考虑，压力梯度项 $\\frac{\\nabla p}{\\rho\\_a}$ 则将在 `pEqn` 中用来约束两相的连续性。\n至此动量方程的每一项都与`UEqn.H`的代码对应起来了。\n\n### 2.2. pEqn\n压力方程的作用是修正两相速度$U\\_a$ 和 $U\\_b$以使速度满足连续性方程。将两相的连续性方程加起来，得到总体的连续性方程如下 **[ *注四* ]**：\n$$\\begin{aligned}\n& \\frac{\\partial \\alpha\\_a}{\\partial t} + \\nabla \\cdot (\\alpha\\_a U\\_a) + \\frac{\\partial \\alpha\\_b}{\\partial t} + \\nabla \\cdot (\\alpha\\_b U\\_b) \\\\\\\\\n= & \\frac{\\partial (\\alpha\\_a+\\alpha\\_b)}{\\partial t} + \\nabla \\cdot (\\alpha\\_a U\\_a+\\alpha\\_b U\\_b) = 0\n\\end{aligned}$$\n由于$\\alpha\\_a+\\alpha\\_b=1$，于是两相连续性方程等价于\n$$\n\\nabla \\cdot (\\alpha\\_a U\\_a+\\alpha\\_b U\\_b) = 0\n$$\n\n再来看压力方程是如何构建起来的。\n完整的动量方程离散后，可以写作如下的统一形式：\n$$\na\\_{p,a}U\\_{p,a}=H(U\\_a)-\\frac{\\nabla p}{\\rho\\_a}+\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b +g\n$$\n\n$$\na\\_{p,b}U\\_{p,b}=H(U\\_b)-\\frac{\\nabla p}{\\rho\\_b}+\\frac{\\alpha\\_a}{\\rho\\_b} K U\\_a +g\n$$\n其中$H(U\\_a)$ 和 $H(U\\_b)$ 包含了动量方程中除 压力梯度项，显式曳力项以及重力项以后所有项的贡献。\n由此离散方程可以得到 $U\\_a$ 和 $U\\_b$ 的表达式如下：\n$$\nU\\_{a}=\\frac{1}{a\\_{p,a}}H(U\\_a)-\\frac{\\nabla p}{a\\_{p,a}\\rho\\_a}+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g\n$$\n\n$$\nU\\_{b}=\\frac{1}{a\\_{p,b}}H(U\\_b)-\\frac{\\nabla p}{a\\_{p,b}\\rho\\_b}+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g\n$$\n\n如果此 $U\\_a$ 和 $U\\_b$ 是方程组的解，那么它们必须满足整体的连续性方程，即\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_a (\\frac{1}{a\\_{p,a}}H(U\\_a)-\\frac{\\nabla p}{a\\_{p,a}\\rho\\_a}+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g)\\right] \\\\\\\\ \n\\+ & \\nabla \\cdot \\left[ \\alpha\\_b (\\frac{1}{a\\_{p,b}}H(U\\_b)-\\frac{\\nabla p}{a\\_{p,b}\\rho\\_b}+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g ) \\right] = 0\n\\end{aligned}$$\n将压力梯度项移到方程的一边，得到\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_a (\\frac{1}{a\\_{p,a}}H(U\\_a)+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g)\\right] + \\nabla \\cdot \\left[ \\alpha\\_b (\\frac{1}{a\\_{p,b}}H(U\\_b)+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g ) \\right] \\\\\\\\\n= &\\nabla \\cdot \\left[ (\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})  \\nabla p \\right ]\n\\end{aligned}$$\n**这便是压力修正方程的原型**。\n在`pEqn.H`中，压力方程其实修正的是界面通量，压力方程迭代收敛以后能保证界面通量的连续性。所以，散度表达式需要根据高斯定理写成界面通量之和的形式：\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_a (\\frac{1}{a\\_{p,a}}H(U\\_a)+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g)\\right] \\\\\\\\\n= & (\\alpha\\_a)\\_f \\left[\\sum\\_f(\\frac{1}{a\\_{p,a}})\\_f H(U\\_a)\\cdot S\\_f + \\sum\\_f(\\frac{\\alpha\\_b K}{ a\\_{p,a} \\rho\\_a})\\_f U\\_b \\cdot S\\_f +\\sum\\_f(\\frac{1}{a\\_{p,a}})\\_f g \\cdot S\\_f \\right ]\n\\end{aligned}$$\n下标 $\\_f$ 表示该项将要在代码中用界面上的变量来表示，在OpenFOAM中，即`surfaceScalarField`，$S\\_f$ 表示界面的面积矢量，下面的公式里也是一样。\n$$\\begin{aligned}\n& \\nabla \\cdot \\left[ \\alpha\\_b (\\frac{1}{a\\_{p,b}}H(U\\_b)+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g)\\right] \\\\\\\\\n= & (\\alpha\\_b)\\_f \\left[\\sum\\_f(\\frac{1}{a\\_{p,b}})\\_f H(U\\_b)\\cdot S\\_f + \\sum\\_f(\\frac{\\alpha\\_a K}{ a\\_{p,b} \\rho\\_b})\\_f U\\_a \\cdot S\\_f +\\sum\\_f(\\frac{1}{a\\_{p,b}})\\_f g \\cdot S\\_f \\right]\n\\end{aligned}$$\n\n以及\n$$\n \\nabla \\cdot \\left[ (\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})  \\nabla p \\right ] = \\sum\\_f (\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})\\_f (\\nabla p) \\cdot S\\_f\n$$\n下面是`pEqn`定义了几个跟界面通量有关的变量：\n```\n    surfaceScalarField alphaf(fvc::interpolate(alpha));\n    surfaceScalarField betaf(scalar(1) - alphaf);\n\n    volScalarField rUaA(1.0/UaEqn.A());\n    volScalarField rUbA(1.0/UbEqn.A());\n\n    phia == (fvc::interpolate(Ua) & mesh.Sf());\n    phib == (fvc::interpolate(Ub) & mesh.Sf());\n\n    rUaAf = fvc::interpolate(rUaA);\n    surfaceScalarField rUbAf(fvc::interpolate(rUbA));\n\n    Ua = rUaA*UaEqn.H();\n    Ub = rUbA*UbEqn.H();\n\n    surfaceScalarField phiDraga\n    (\n        fvc::interpolate(beta/rhoa*K*rUaA)*phib + rUaAf*(g & mesh.Sf())\n    );\n\n    \n    surfaceScalarField phiDragb\n    (\n        fvc::interpolate(alpha/rhob*K*rUbA)*phia + rUbAf*(g & mesh.Sf())\n    );\n\n\n    phia = (fvc::interpolate(Ua) & mesh.Sf()) + fvc::ddtPhiCorr(rUaA, Ua, phia) + phiDraga;\n    phib = (fvc::interpolate(Ub) & mesh.Sf()) + fvc::ddtPhiCorr(rUbA, Ub, phib) + phiDragb;\n\n    phi = alphaf*phia + betaf*phib;\n\n    surfaceScalarField Dp\n    (\n        \"(rho*(1|A(U)))\",\n        alphaf*rUaAf/rhoa + betaf*rUbAf/rhob\n    );\n\n```\n`phiDraga` 和 `phiDragb` 分别对应 $(\\frac{\\alpha\\_b K}{ a\\_{p,a} \\rho\\_a})\\_f U\\_b \\cdot S\\_f +(\\frac{1}{a\\_{p,a}})\\_f g \\cdot S\\_f$ 和 $(\\frac{\\alpha\\_a K}{ a\\_{p,b} \\rho\\_b})\\_f U\\_a \\cdot S\\_f +(\\frac{1}{a\\_{p,b}})\\_f g \\cdot S\\_f$\n\n由于13-14行的定义，28-29行中的 `(fvc::interpolate(Ua) & mesh.Sf())` 和 `(fvc::interpolate(Ub) & mesh.Sf())` 便分别对应的是 $(\\frac{1}{a\\_{p,a}})\\_f H(U\\_a)\\cdot S\\_f$ 和 $(\\frac{1}{a\\_{p,b}})\\_f H(U\\_b)\\cdot S\\_f$ 。\n\n有了上面的定义，可以看出31行定义的`phi=alphaf*phia + betaf*phib`便表示了压力方程的左边。\n\n再看33-36行定义的`Dp`，很显然，表示的是压力方程右边的$(\\frac{\\alpha\\_a }{a\\_{p,a}\\rho\\_a} + \\frac{\\alpha\\_b }{a\\_{p,b}\\rho\\_b})\\_f$。\n\n有了以上的定义，便可以构建用于修正界面通量的压力方程了：\n```\nfvScalarMatrix pEqn\n  (\n     fvm::laplacian(Dp, p) == fvc::div(phi)\n  );\n\n```\n如上所述，`pEqn`收敛以后，得到的就是满足连续性的界面通量了，然后再利用求得的界面通量来修正两相的速度，便得到了满足两相连续性的速度：\n```\nUa += fvc::reconstruct(phiDraga - rUaAf*SfGradp/rhoa);\nUa.correctBoundaryConditions();\n\nUb += fvc::reconstruct(phiDragb - rUbAf*SfGradp/rhob);\nUb.correctBoundaryConditions();\n\nU = alpha*Ua + beta*Ub;\n```\n注意，`Ua`和`Ub`为什么是这样来修正呢？回想上面变量定义那个代码段的13-14行，这两行将`Ua`和`Ub`分别定义成了$\\frac{1}{a\\_{p,a}} H(U\\_a)$ 和 $\\frac{1}{a\\_{p,b}} H(U\\_b)$。\n回想`Ua`和`Ub`的离散方程的统一形式\n$$\nU\\_{a}=\\frac{1}{a\\_{p,a}}H(U\\_a)-\\frac{\\nabla p}{a\\_{p,a}\\rho\\_a}+\\frac{\\alpha\\_b}{ a\\_{p,a} \\rho\\_a} K U\\_b +\\frac{1}{a\\_{p,a}} g\n$$\n\n$$\nU\\_{b}=\\frac{1}{a\\_{p,b}}H(U\\_b)-\\frac{\\nabla p}{a\\_{p,b}\\rho\\_b}+\\frac{\\alpha\\_a}{ a\\_{p,b} \\rho\\_b} K U\\_a +\\frac{1}{a\\_{p,b}} g\n$$\n\n会发现13-14行定义的`Ua`和`Ub`都少了几项，所以缺了的这几项的贡献需要在速度修正步骤加回来，而`Ua+=`后面的`fvc::reconstruct(phiDraga - rUaAf*SfGradp/rhoa)`刚好就对应着`Ua`缺少的那几项。因为经过压力方程修正以后，界面通量是连续的，所以，将缺失的几项对应的界面通量通过`reconstruct`函数从界面通量重构从对体中心的速度的贡献，便得到了满足连续性的体中心速度了。对`Ub`也是同样的。\n\n经过以上步骤，便能得到满足整体连续性的两相速度`Ua` 和 `Ub`了。\n\n\n## 注释\n**注一**：OpenFOAM 里的`div`函数，字面意义上看起来好像是散度的意思，实际上，`div`函数执行的是**加和**运算。举例说，对于`fvc::div(phia)`，`phia`是`surfaceScalarField`，其值为`(fvc::interpolate(Ua) & mesh.Sf())`，即将存储在体中心的`Ua`插值到每个网格对应的面的面心，然后用面心的速度与该面的面积矢量点乘。从代码中看，`fvc::div(phia)`对应的是 $\\nabla \\cdot U\\_a$，根据高斯定理，也就是 $\\sum\\_f (U\\_a)\\_f \\cdot S\\_f$，而`phia`对应着 $(U\\_a)\\_f \\cdot S\\_f$，所以，`fvc::div(phia)`实际进行的运算是将包围每个网格的面上的通量加起来。更详细的说明见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)。\n**注二**：注意这里的$\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ 在代码中的表示方法，详细说明见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)。\n**注三**：注意这里的$\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ 在代码中的表示方法，以及与上一个$\\frac{\\nabla \\alpha\\_a}{\\alpha\\_a}$ 的区别，详细说明见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)。\n**注四**：这里说的总体的连续性方程指的是**总体体积的守恒**，而不是总体质量的守恒，这二者的差异见Henrik Rusche 的 PHD 论文 P112 的说明。\n\n## 参考资料\n1.  Henrik Rusche， PHD Thesis， Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase Fractions， Imperial College of Science, Technology & Medicine, Department of Mechanical Engineering, 2002\n2. https://openfoamwiki.net/index.php/BubbleFoam\n3. http://www.cfd-online.com/Forums/openfoam-solving/71141-rewriting-twophaseeulerfoam-conservative-form.html\n\n","slug":"twoPhaseEulerFoam2","published":1,"updated":"2015-06-27T12:42:13.207Z","layout":"post","photos":[],"link":"","_id":"cioiqegbk0013z8mboot1zxyt"},{"title":"twoPhaseEulerFoam 全解读之一","date":"2015-05-17T06:46:07.000Z","comments":1,"_content":"\n本系列将对OpenFOAM-2.1.1 中的 `twoPhaseEulerFoam` 求解器进行完全解读，共分三部分：方程推导，代码解读，补充说明。本篇进行方程推导，详细介绍如果从双流体模型出发得到 `twoPhaseEulerFoam` 中的 `UEqn.H` 对应的模型方程形式。\n\n<!--more-->\n\n## 1. 方程推导\n双流体模型方程可以表达成如下形式：\n\n**连续性方程**：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)=0$$\n**动量守恒方程**:\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi)+\\nabla\\cdot(\\alpha\\_\\phi\\tau\\_\\phi)+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi R\\_\\phi )=-\\alpha\\_\\phi\\nabla p+\\alpha\\_\\phi\\rho\\_\\phi g+M\\_\\phi$$\n式中，下标$\\phi=a,b$分别代表分散相和连续相，$\\tau\\_\\phi$表示粘性应力项，$R\\_\\phi$表示雷诺应力项，$M\\_\\phi$表示相间作用项。\n上述方程是完全守恒形式的，但是注意到上述动量方程的瞬变项是$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}$，等于说解这个方程能得到的是每个时间步的动量，若要转化成速度，则需要用动量除以密度与体积分率的乘积，即$\\frac{(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\alpha\\_\\phi\\rho\\_\\phi}$。那么当离散相a的体积分率$\\alpha\\_a\\to0$时，这个除法就要出问题了。于是，Weller [1] 提出通过构造一种\"phase-intensive\"形式的动量方程来避开这个问题，见下面的详细推导。\nWeller提出的方法的核心是将$\\alpha\\_\\phi\\rho\\_\\phi$从动量方程的瞬变项中剥离出来，以使动量方程直接对速度进行演化，而不是动量。\n首先对动量方程的瞬变项和对流项进行如下转化：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}=\\alpha\\_\\phi\\rho\\_\\phi\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi )}{\\partial t}$$\n\n$$\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi)= \\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi\\cdot \\nabla( U\\_\\phi) + U\\_\\phi\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi  U\\_\\phi)$$\n注：这里到了张量运算公式$[\\nabla\\cdot \\mathbf{vw}]=[\\mathbf{v}\\cdot\\nabla\\mathbf{w}]+\\mathbf{w}(\\nabla\\cdot\\mathbf{v})$，具体可参考 Bird 的 Transport Phenomenon 的 Appendix A。\n\n于是，瞬变项和对流项的加和可以写成如下形式：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi)=\\alpha\\_\\phi\\rho\\_\\phi\\left[\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi \\cdot \\nabla( U\\_\\phi)\\right]+U\\_\\phi \\left[ \\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi )}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi  U\\_\\phi)\\right]$$\n注意右边第二项的括号里其实就是连续性方程的左边，其值为0，因此得到：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi) = \\alpha\\_\\phi\\rho\\_\\phi\\left[\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi \\cdot \\nabla( U\\_\\phi)\\right]$$\n\n于是得到第一步转化之后的动量方程：\n$$\\alpha\\_\\phi\\rho\\_\\phi\\left[\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi\\cdot\\nabla( U\\_\\phi)\\right] + \\nabla\\cdot(\\alpha\\_\\phi\\tau\\_\\phi) + \\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi R\\_\\phi ) = -\\alpha\\_\\phi\\nabla p + \\alpha\\_\\phi\\rho\\_\\phi g + M\\_\\phi$$\n\n下面处理粘性应力项和雷诺应力项。\n\n$$\\nabla\\cdot(\\alpha\\_\\phi\\tau\\_\\phi) + \\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi R\\_\\phi )=\\nabla\\cdot\\left[\\alpha\\_\\phi\\rho\\_\\phi(\\frac{\\tau\\_\\phi}{\\rho\\_\\phi}+R\\_\\phi)\\right] = \\nabla\\cdot\\left[\\alpha\\_\\phi\\rho\\_\\phi R\\_{eff,\\phi}\\right ]$$\n\n其中 $R\\_{eff,\\phi}=\\frac{\\tau\\_\\phi}{\\rho\\_\\phi}+R\\_\\phi$。\n\n根据定义(此处参考[BubbleFoam的Wiki页面](https://openfoamwiki.net/index.php/BubbleFoam))：\n$$\n\\boldsymbol{\\tau}\\_{\\phi} = - \\rho\\_{\\phi} \\nu\\_{\\phi} \\left[\\nabla \\mathbf{U}\\_{\\phi} + \\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right] + \\frac{2}{3}\\rho\\_{\\phi}\\nu\\_{\\phi} \\left( \\nabla \\cdot \\mathbf{U}\\_{\\phi} \\right) \\mathbf{I}\n$$\n以及\n$$\n\\mathbf{R}\\_{\\phi} = -  \\nu\\_{\\phi,\\textrm{t}} \\left[ \\nabla \\mathbf{U}\\_{\\phi} +\\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right] + \\frac{2}{3}  \\nu\\_{\\phi,\\textrm{t}} \\left( \\nabla \\cdot \\mathbf{U}\\_{\\phi} \\right) \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I}\n$$\n代入到 $R\\_{eff,\\phi}$中，得：\n$$R\\_{eff,\\phi}=-(\\nu\\_\\phi+\\nu\\_{\\phi , t})\\left[ \\nabla \\mathbf{U}\\_{\\phi} +\\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right]+\\frac{2}{3}(\\nu\\_\\phi+\\nu\\_{\\phi , t}) \\left (\\nabla \\cdot \\mathbf{U}\\_{\\phi}\\right )  \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I}$$\n令 $\\nu\\_{eff}=\\nu\\_\\phi+\\nu\\_{\\phi , t}$ ，则：\n$$\nR\\_{eff,\\phi}=-\\nu\\_{eff}\\left[ \\nabla \\mathbf{U}\\_{\\phi} +\\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right]+\\frac{2}{3}\\nu\\_{eff}\n\\left (\\nabla \\cdot \\mathbf{U}\\_{\\phi}\\right )  \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I} = -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\n$$\n其中$$R\\_{c,\\phi}=-\\nu\\_{eff} \\nabla \\mathbf{U}^\\textrm{T}\\_{\\phi}+\\frac{2}{3}\\nu\\_{eff}\n\\left (\\nabla \\cdot \\mathbf{U}\\_{\\phi}\\right )  \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I}$$\n\n于是得到：\n $$\\begin{aligned}\n\\nabla\\cdot\\left[\\alpha\\_\\phi\\rho\\_\\phi R\\_{eff,\\phi}\\right ] = & \\nabla(\\alpha\\_\\phi\\rho\\_\\phi)\\cdot\\left[ R\\_{eff,\\phi}\\right] + \\alpha\\_\\phi\\rho\\_\\phi\\nabla\\cdot \\left [ R\\_{eff,\\phi}\\right ]\\\\\\\\\n=& \\alpha\\_\\phi\\rho\\_\\phi\\nabla\\cdot\\left[ -\\nu\\_{eff}\\nabla U\\_\\phi\\right] + \\alpha\\_\\phi\\rho\\_\\phi\\nabla\\cdot\\left[ R\\_{c,\\phi}\\right] + \\nabla(\\alpha\\_\\phi\\rho\\_\\phi)\\left[ -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\\right]\n\\end{aligned}\n $$\n \n代入到动量方程中，并且方程两边同时除以$\\alpha\\_\\phi\\rho\\_\\phi$，得到：\n $$\n \\frac{\\partial U\\_\\phi}{\\partial t} + U\\_\\phi\\cdot\\nabla U\\_\\phi -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_\\phi \\right ] + \\nabla \\cdot \\left[ R\\_{c,\\phi}\\right] + \\frac{\\nabla(\\alpha\\_\\phi\\rho\\_\\phi)}{\\alpha\\_\\phi\\rho\\_\\phi}\\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\\right] = -\\frac{\\nabla p}{\\rho\\_\\phi} + g + \\frac{M\\_\\phi}{\\alpha\\_\\phi\\rho\\_\\phi}\n $$\n\n如果假定两相流体均为不可压缩，密度恒为常数，于是可以得到不可压缩的双流体模型的方程组：\n\n**连续性方程**\n$$\\frac{\\partial(\\alpha\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi U\\_\\phi)=0$$\n\n**动量方程**\n $$\n \\frac{\\partial U\\_\\phi}{\\partial t} + U\\_\\phi\\cdot\\nabla U\\_\\phi -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_\\phi \\right ] + \\nabla \\cdot \\left[ R\\_{c,\\phi}\\right] + \\frac{\\nabla(\\alpha\\_\\phi)}{\\alpha\\_\\phi} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\\right] = -\\frac{\\nabla p}{\\rho\\_\\phi} + g + \\frac{M\\_\\phi}{\\alpha\\_\\phi\\rho\\_\\phi}\n $$\n \n方程中还剩下相间作用项没有处理，对于分散相和连续项形式，相间作用力是大小相等符号想反，这里只考虑分散相的形式，令$\\phi=a$，则得到分散相的动量方程：\n$$\n \\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot\\nabla U\\_a -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] = -\\frac{\\nabla p}{\\rho\\_a} + g + \\frac{M\\_a}{\\alpha\\_a\\rho\\_a}\n $$\n \n 相间作用只考虑曳力，升力以及虚拟质量力，即$M,a=M\\_{drag}+M\\_{lift}+M\\_{vm}$，下面分别考虑每一种相间作用力。\n \n + 曳力\n  $M\\_{drag}=-\\beta(U\\_a-U\\_b)$，其中$\\beta$为曳力系数。\n + 升力\n  $M\\_{lift}=-\\alpha\\_a\\alpha\\_b C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a)U\\_r \\times (\\nabla \\times U)$ ，其中 $U\\_r=U\\_a-U\\_b$，$U=\\alpha\\_a U\\_a + \\alpha\\_b U\\_b$\n + 虚拟质量力\n  $M\\_{vm}=\\alpha\\_a\\alpha\\_b C\\_{vm}\\rho\\_b\\left[ \\frac{DU\\_b}{Dt}-\\frac{DU\\_a}{Dt}\\right]$，其中$\\frac{D}{Dt}$表示物质导数，$\\frac{DU\\_b}{Dt}=\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b$，$\\frac{DU\\_a}{Dt}=\\frac{\\partial U\\_a}{\\partial t}+U\\_a \\cdot \\nabla U\\_a$\n\n考虑到形式的统一，令$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$，则曳力可表示为$M\\_{drag}=-\\alpha\\_a\\alpha\\_b K(U\\_a-U\\_b)$\n\n代入到分散相a的动量方程中，得到：\n$$\\begin{aligned}\n&\\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot \\nabla U\\_a -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\nabla p}{\\rho\\_a} + g - \\frac{\\alpha\\_b}{\\rho\\_a} K (U\\_a-U\\_b) - \\frac{\\alpha\\_b}{\\rho\\_a}\n C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) \\\\\\\\\n +&  \\frac{\\alpha\\_b}{\\rho\\_a} C\\_{vm}\\rho\\_b\\left[ \\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b - (\\frac{\\partial U\\_a}{\\partial t}+U\\_a \\cdot \\nabla U\\_a)\\right] \n \\end{aligned}$$\n\n将相关的项合并，并调整顺序，便得到与`twoPhaseEulerFoam`求解器的`UEqn.H`文件中相同形式的分散相动量方程：\n$$\n\\begin{aligned}\n&(1+\\frac{\\alpha\\_b \\rho\\_b}{\\rho\\_a} C\\_{vm})(\\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot \\nabla U\\_a ) -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a - \\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\} \\\\\\\\\n\\- & \\frac{\\nabla p}{\\rho\\_a} + g + \\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b \n\\end{aligned}\n$$\n\n连续相b的动量方程形式相仿，这里就不再重复了。这里有几点**注意事项**：\n1. 此处的双流体模型在推导的过程中，是把a当作分散相，b当作连续相的。分散相的体积分率$\\alpha\\_a$可以等于0，但是连续项体积分率$\\alpha\\_b$不能等于0 ，否则会出问题。\n2. 曳力系数 $\\beta$ 的形式就是文献中常见的形式，比如，WenYu 曳力系数 $\\beta=\\frac{3}{4}\\frac{(1-\\alpha\\_b)\\alpha\\_b}{d\\_{p,a}}|U\\_b-U\\_a|C\\_{D0}\\alpha\\_b^{-2.7}$，Ergun 曳力系数 $\\beta=150\\frac{(1-\\alpha\\_b)^2\\mu\\_b}{\\alpha\\_b d\\_a^2}+1.75\\frac{(1-\\alpha\\_b)\\rho\\_b{U\\_b-U\\_a}}{d\\_a}$。而程序中定义的$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$，所以，当$\\alpha\\_b\\to 0$时，如果用WenYu曳力那还不会出错，因为曳力系数中的分子里同时含有$\\alpha\\_a\\alpha\\_b$，运算$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$不会出现除以0的问题；但如果用Ergun曳力，那就要出问题了，因为Ergun曳力系数中两项的分子都没有$\\alpha\\_b$，所以运算$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$就要出问题了。\n\n\n## 参考资料\n1. Henrik Rusche， PHD Thesis， Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase Fractions， Imperial College of Science, Technology & Medicine, Department of Mechanical Engineering, 2002\n2. https://openfoamwiki.net/index.php/BubbleFoam\n3. http://dyfluid.com/pdf/%E5%8F%8C%E6%B5%81%E4%BD%93%E6%A8%A1%E5%9E%8B%E5%9C%A8OpenFOAM%E4%B8%AD%E7%9A%84%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pdf\n4. http://www.cfd-online.com/Forums/openfoam-solving/71141-rewriting-twophaseeulerfoam-conservative-form.html\n","source":"_posts/twoPhaseEulerFoam1.md","raw":"title: \"twoPhaseEulerFoam 全解读之一\"\ndate: 2015-05-17 14:46:07\ncomments: true\ntags:\n  - OpenFOAM\n  - Code Explained\ncategories:\n - OpenFOAM\n---\n\n本系列将对OpenFOAM-2.1.1 中的 `twoPhaseEulerFoam` 求解器进行完全解读，共分三部分：方程推导，代码解读，补充说明。本篇进行方程推导，详细介绍如果从双流体模型出发得到 `twoPhaseEulerFoam` 中的 `UEqn.H` 对应的模型方程形式。\n\n<!--more-->\n\n## 1. 方程推导\n双流体模型方程可以表达成如下形式：\n\n**连续性方程**：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)=0$$\n**动量守恒方程**:\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi)+\\nabla\\cdot(\\alpha\\_\\phi\\tau\\_\\phi)+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi R\\_\\phi )=-\\alpha\\_\\phi\\nabla p+\\alpha\\_\\phi\\rho\\_\\phi g+M\\_\\phi$$\n式中，下标$\\phi=a,b$分别代表分散相和连续相，$\\tau\\_\\phi$表示粘性应力项，$R\\_\\phi$表示雷诺应力项，$M\\_\\phi$表示相间作用项。\n上述方程是完全守恒形式的，但是注意到上述动量方程的瞬变项是$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}$，等于说解这个方程能得到的是每个时间步的动量，若要转化成速度，则需要用动量除以密度与体积分率的乘积，即$\\frac{(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\alpha\\_\\phi\\rho\\_\\phi}$。那么当离散相a的体积分率$\\alpha\\_a\\to0$时，这个除法就要出问题了。于是，Weller [1] 提出通过构造一种\"phase-intensive\"形式的动量方程来避开这个问题，见下面的详细推导。\nWeller提出的方法的核心是将$\\alpha\\_\\phi\\rho\\_\\phi$从动量方程的瞬变项中剥离出来，以使动量方程直接对速度进行演化，而不是动量。\n首先对动量方程的瞬变项和对流项进行如下转化：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}=\\alpha\\_\\phi\\rho\\_\\phi\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi )}{\\partial t}$$\n\n$$\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi)= \\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi\\cdot \\nabla( U\\_\\phi) + U\\_\\phi\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi  U\\_\\phi)$$\n注：这里到了张量运算公式$[\\nabla\\cdot \\mathbf{vw}]=[\\mathbf{v}\\cdot\\nabla\\mathbf{w}]+\\mathbf{w}(\\nabla\\cdot\\mathbf{v})$，具体可参考 Bird 的 Transport Phenomenon 的 Appendix A。\n\n于是，瞬变项和对流项的加和可以写成如下形式：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi)=\\alpha\\_\\phi\\rho\\_\\phi\\left[\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi \\cdot \\nabla( U\\_\\phi)\\right]+U\\_\\phi \\left[ \\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi )}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi  U\\_\\phi)\\right]$$\n注意右边第二项的括号里其实就是连续性方程的左边，其值为0，因此得到：\n$$\\frac{\\partial(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi U\\_\\phi U\\_\\phi) = \\alpha\\_\\phi\\rho\\_\\phi\\left[\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi \\cdot \\nabla( U\\_\\phi)\\right]$$\n\n于是得到第一步转化之后的动量方程：\n$$\\alpha\\_\\phi\\rho\\_\\phi\\left[\\frac{\\partial( U\\_\\phi)}{\\partial t}+U\\_\\phi\\cdot\\nabla( U\\_\\phi)\\right] + \\nabla\\cdot(\\alpha\\_\\phi\\tau\\_\\phi) + \\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi R\\_\\phi ) = -\\alpha\\_\\phi\\nabla p + \\alpha\\_\\phi\\rho\\_\\phi g + M\\_\\phi$$\n\n下面处理粘性应力项和雷诺应力项。\n\n$$\\nabla\\cdot(\\alpha\\_\\phi\\tau\\_\\phi) + \\nabla\\cdot(\\alpha\\_\\phi\\rho\\_\\phi R\\_\\phi )=\\nabla\\cdot\\left[\\alpha\\_\\phi\\rho\\_\\phi(\\frac{\\tau\\_\\phi}{\\rho\\_\\phi}+R\\_\\phi)\\right] = \\nabla\\cdot\\left[\\alpha\\_\\phi\\rho\\_\\phi R\\_{eff,\\phi}\\right ]$$\n\n其中 $R\\_{eff,\\phi}=\\frac{\\tau\\_\\phi}{\\rho\\_\\phi}+R\\_\\phi$。\n\n根据定义(此处参考[BubbleFoam的Wiki页面](https://openfoamwiki.net/index.php/BubbleFoam))：\n$$\n\\boldsymbol{\\tau}\\_{\\phi} = - \\rho\\_{\\phi} \\nu\\_{\\phi} \\left[\\nabla \\mathbf{U}\\_{\\phi} + \\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right] + \\frac{2}{3}\\rho\\_{\\phi}\\nu\\_{\\phi} \\left( \\nabla \\cdot \\mathbf{U}\\_{\\phi} \\right) \\mathbf{I}\n$$\n以及\n$$\n\\mathbf{R}\\_{\\phi} = -  \\nu\\_{\\phi,\\textrm{t}} \\left[ \\nabla \\mathbf{U}\\_{\\phi} +\\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right] + \\frac{2}{3}  \\nu\\_{\\phi,\\textrm{t}} \\left( \\nabla \\cdot \\mathbf{U}\\_{\\phi} \\right) \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I}\n$$\n代入到 $R\\_{eff,\\phi}$中，得：\n$$R\\_{eff,\\phi}=-(\\nu\\_\\phi+\\nu\\_{\\phi , t})\\left[ \\nabla \\mathbf{U}\\_{\\phi} +\\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right]+\\frac{2}{3}(\\nu\\_\\phi+\\nu\\_{\\phi , t}) \\left (\\nabla \\cdot \\mathbf{U}\\_{\\phi}\\right )  \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I}$$\n令 $\\nu\\_{eff}=\\nu\\_\\phi+\\nu\\_{\\phi , t}$ ，则：\n$$\nR\\_{eff,\\phi}=-\\nu\\_{eff}\\left[ \\nabla \\mathbf{U}\\_{\\phi} +\\nabla^{\\textrm{T}} \\mathbf{U}\\_{\\phi} \\right]+\\frac{2}{3}\\nu\\_{eff}\n\\left (\\nabla \\cdot \\mathbf{U}\\_{\\phi}\\right )  \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I} = -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\n$$\n其中$$R\\_{c,\\phi}=-\\nu\\_{eff} \\nabla \\mathbf{U}^\\textrm{T}\\_{\\phi}+\\frac{2}{3}\\nu\\_{eff}\n\\left (\\nabla \\cdot \\mathbf{U}\\_{\\phi}\\right )  \\mathbf{I} + \\frac{2}{3} k\\_{\\phi} \\mathbf{I}$$\n\n于是得到：\n $$\\begin{aligned}\n\\nabla\\cdot\\left[\\alpha\\_\\phi\\rho\\_\\phi R\\_{eff,\\phi}\\right ] = & \\nabla(\\alpha\\_\\phi\\rho\\_\\phi)\\cdot\\left[ R\\_{eff,\\phi}\\right] + \\alpha\\_\\phi\\rho\\_\\phi\\nabla\\cdot \\left [ R\\_{eff,\\phi}\\right ]\\\\\\\\\n=& \\alpha\\_\\phi\\rho\\_\\phi\\nabla\\cdot\\left[ -\\nu\\_{eff}\\nabla U\\_\\phi\\right] + \\alpha\\_\\phi\\rho\\_\\phi\\nabla\\cdot\\left[ R\\_{c,\\phi}\\right] + \\nabla(\\alpha\\_\\phi\\rho\\_\\phi)\\left[ -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\\right]\n\\end{aligned}\n $$\n \n代入到动量方程中，并且方程两边同时除以$\\alpha\\_\\phi\\rho\\_\\phi$，得到：\n $$\n \\frac{\\partial U\\_\\phi}{\\partial t} + U\\_\\phi\\cdot\\nabla U\\_\\phi -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_\\phi \\right ] + \\nabla \\cdot \\left[ R\\_{c,\\phi}\\right] + \\frac{\\nabla(\\alpha\\_\\phi\\rho\\_\\phi)}{\\alpha\\_\\phi\\rho\\_\\phi}\\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\\right] = -\\frac{\\nabla p}{\\rho\\_\\phi} + g + \\frac{M\\_\\phi}{\\alpha\\_\\phi\\rho\\_\\phi}\n $$\n\n如果假定两相流体均为不可压缩，密度恒为常数，于是可以得到不可压缩的双流体模型的方程组：\n\n**连续性方程**\n$$\\frac{\\partial(\\alpha\\_\\phi)}{\\partial t}+\\nabla\\cdot(\\alpha\\_\\phi U\\_\\phi)=0$$\n\n**动量方程**\n $$\n \\frac{\\partial U\\_\\phi}{\\partial t} + U\\_\\phi\\cdot\\nabla U\\_\\phi -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_\\phi \\right ] + \\nabla \\cdot \\left[ R\\_{c,\\phi}\\right] + \\frac{\\nabla(\\alpha\\_\\phi)}{\\alpha\\_\\phi} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_\\phi + R\\_{c,\\phi}\\right] = -\\frac{\\nabla p}{\\rho\\_\\phi} + g + \\frac{M\\_\\phi}{\\alpha\\_\\phi\\rho\\_\\phi}\n $$\n \n方程中还剩下相间作用项没有处理，对于分散相和连续项形式，相间作用力是大小相等符号想反，这里只考虑分散相的形式，令$\\phi=a$，则得到分散相的动量方程：\n$$\n \\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot\\nabla U\\_a -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] = -\\frac{\\nabla p}{\\rho\\_a} + g + \\frac{M\\_a}{\\alpha\\_a\\rho\\_a}\n $$\n \n 相间作用只考虑曳力，升力以及虚拟质量力，即$M,a=M\\_{drag}+M\\_{lift}+M\\_{vm}$，下面分别考虑每一种相间作用力。\n \n + 曳力\n  $M\\_{drag}=-\\beta(U\\_a-U\\_b)$，其中$\\beta$为曳力系数。\n + 升力\n  $M\\_{lift}=-\\alpha\\_a\\alpha\\_b C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a)U\\_r \\times (\\nabla \\times U)$ ，其中 $U\\_r=U\\_a-U\\_b$，$U=\\alpha\\_a U\\_a + \\alpha\\_b U\\_b$\n + 虚拟质量力\n  $M\\_{vm}=\\alpha\\_a\\alpha\\_b C\\_{vm}\\rho\\_b\\left[ \\frac{DU\\_b}{Dt}-\\frac{DU\\_a}{Dt}\\right]$，其中$\\frac{D}{Dt}$表示物质导数，$\\frac{DU\\_b}{Dt}=\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b$，$\\frac{DU\\_a}{Dt}=\\frac{\\partial U\\_a}{\\partial t}+U\\_a \\cdot \\nabla U\\_a$\n\n考虑到形式的统一，令$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$，则曳力可表示为$M\\_{drag}=-\\alpha\\_a\\alpha\\_b K(U\\_a-U\\_b)$\n\n代入到分散相a的动量方程中，得到：\n$$\\begin{aligned}\n&\\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot \\nabla U\\_a -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\nabla p}{\\rho\\_a} + g - \\frac{\\alpha\\_b}{\\rho\\_a} K (U\\_a-U\\_b) - \\frac{\\alpha\\_b}{\\rho\\_a}\n C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) \\\\\\\\\n +&  \\frac{\\alpha\\_b}{\\rho\\_a} C\\_{vm}\\rho\\_b\\left[ \\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b - (\\frac{\\partial U\\_a}{\\partial t}+U\\_a \\cdot \\nabla U\\_a)\\right] \n \\end{aligned}$$\n\n将相关的项合并，并调整顺序，便得到与`twoPhaseEulerFoam`求解器的`UEqn.H`文件中相同形式的分散相动量方程：\n$$\n\\begin{aligned}\n&(1+\\frac{\\alpha\\_b \\rho\\_b}{\\rho\\_a} C\\_{vm})(\\frac{\\partial U\\_a}{\\partial t} + U\\_a\\cdot \\nabla U\\_a ) -\\nabla \\cdot \\left[ \\nu\\_{eff} \\nabla U\\_a \\right ] + \\nabla \\cdot \\left[ R\\_{c,a}\\right] + \\frac{\\nabla(\\alpha\\_a)}{\\alpha\\_a} \\cdot \\left[ -\\nu\\_{eff}\\nabla U\\_a + R\\_{c,a}\\right] \\\\\\\\\n= & -\\frac{\\alpha\\_b}{\\rho\\_a} K U\\_a - \\frac{\\alpha\\_b}{\\rho\\_a} \\left\\\\{ {C\\_l (\\alpha\\_b \\rho\\_b + \\alpha\\_a \\rho\\_a) U\\_r \\times (\\nabla \\times U) -  C\\_{vm}\\rho\\_b\\left[ {\\frac{\\partial U\\_b}{\\partial t} + U\\_b \\cdot \\nabla U\\_b }\\right] } \\right\\\\} \\\\\\\\\n\\- & \\frac{\\nabla p}{\\rho\\_a} + g + \\frac{\\alpha\\_b}{\\rho\\_a} K U\\_b \n\\end{aligned}\n$$\n\n连续相b的动量方程形式相仿，这里就不再重复了。这里有几点**注意事项**：\n1. 此处的双流体模型在推导的过程中，是把a当作分散相，b当作连续相的。分散相的体积分率$\\alpha\\_a$可以等于0，但是连续项体积分率$\\alpha\\_b$不能等于0 ，否则会出问题。\n2. 曳力系数 $\\beta$ 的形式就是文献中常见的形式，比如，WenYu 曳力系数 $\\beta=\\frac{3}{4}\\frac{(1-\\alpha\\_b)\\alpha\\_b}{d\\_{p,a}}|U\\_b-U\\_a|C\\_{D0}\\alpha\\_b^{-2.7}$，Ergun 曳力系数 $\\beta=150\\frac{(1-\\alpha\\_b)^2\\mu\\_b}{\\alpha\\_b d\\_a^2}+1.75\\frac{(1-\\alpha\\_b)\\rho\\_b{U\\_b-U\\_a}}{d\\_a}$。而程序中定义的$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$，所以，当$\\alpha\\_b\\to 0$时，如果用WenYu曳力那还不会出错，因为曳力系数中的分子里同时含有$\\alpha\\_a\\alpha\\_b$，运算$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$不会出现除以0的问题；但如果用Ergun曳力，那就要出问题了，因为Ergun曳力系数中两项的分子都没有$\\alpha\\_b$，所以运算$K=\\frac{\\beta}{\\alpha\\_a\\alpha\\_b}$就要出问题了。\n\n\n## 参考资料\n1. Henrik Rusche， PHD Thesis， Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase Fractions， Imperial College of Science, Technology & Medicine, Department of Mechanical Engineering, 2002\n2. https://openfoamwiki.net/index.php/BubbleFoam\n3. http://dyfluid.com/pdf/%E5%8F%8C%E6%B5%81%E4%BD%93%E6%A8%A1%E5%9E%8B%E5%9C%A8OpenFOAM%E4%B8%AD%E7%9A%84%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pdf\n4. http://www.cfd-online.com/Forums/openfoam-solving/71141-rewriting-twophaseeulerfoam-conservative-form.html\n","slug":"twoPhaseEulerFoam1","published":1,"updated":"2015-05-17T07:16:05.037Z","layout":"post","photos":[],"link":"","_id":"cioiqegbo0017z8mb7r2qzf5i"},{"title":"两个简单的随时间变化的边界条件","date":"2015-12-12T06:38:50.000Z","comments":1,"_content":"\n这一篇里介绍OpenFOAM自带的两个简单的随时间变化的边界条件： `uniformFixedValue` 和 `flowRateInletVelocity` 。\n\n<!--more-->\n\n#### 1. uniformFixedValue\n这个边界属于第一类边界，即直接指定边界上某个量的值。跟 `fixedValue` 的不同之处在于，这个边界条件允许边界上的值随着时间变化。一般的用法如下：\n\n```\n        type            uniformFixedValue;\n        uniformValue     table\n        (\n            (0.000 (0 0 0.002))\n            (0.010 (0 0 0.002))\n            (0.011 (0.011 0 0.011))\n            (0.100 (0.10 0 0.100))\n        );\n```\n上面这一段指定的是速度 U （也可以是其他矢量，或者标量）的边界条件，通过一个 table 来定义边界随时间的变化行为，具体解释如下（假定时间步长等于0.001 s）：\n\n+ 当 0.00 <= t <= 0.01 s 时，速度值为 (0   0   0.002);\n+ 当 t = 0.011 s 时，速度变为 (0.011   0   0.011);\n+ 0.011 <= t <= 0.100 之间，速度的每个分量均为线性变化的;\n+ 当 t > 0.100 s 以后，速度保持  (0.10   0   0.100) 不变。\n\n除了通过 table 来定义随时间分段线性变化边界条件，还可以定义多项式变化的边界，比如：\n\n```\n            type uniformFixedValue;\n            uniformValue polynomial\n            (\n                ((1 0 0) (0 0 0))\n                ((2 2 0) (1 2 0))\n                ((3 5 0) (2 4 0))\n            );\n```\n以上这段定义的是这样一个边界：x 分量，$U\\_x=1+2\\cdot t+3\\cdot t^2$；y 分量，$U\\_y=2\\cdot t^2+5\\cdot t^4$; z 分量，$U\\_z=0$。\n需要注意的是， `polynomial` 这种方式，目前测试在 2.3.x 以及以下版本不能使用，在最新的 3.0.x 版中可以使用。\n\n此外，还有其他的一些定义方式，列举如下（参考[这个网站](http://www.geocities.jp/penguinitis2002/study/OpenFOAM/time_varying_bc.html)）：\n##### 固定值\n```\ninlet\n{ \n    type uniformFixedValue; \n    uniformValue constant (10 0 0);\n}\n```\n这种方式与 `fixedValue` 效果是一样的。\n##### tableFile\n```\ninlet \n{ \n    type uniformFixedValue; \n    uniformValue tableFile; \n    tableFileCoeffs\n    { \n\tfileName \"$FOAM_CASE/velocity\" \n\toutOfBounds clamp; \n    } \n}\n```\n这个跟 `table` 那种类似，只不过将上面 `table` 的内容写到一个文件里了（这里是 `velocity` 文件）。 `velocity` 文件的格式为\n```\n(\n    (0   (0 0 0))\n    (0.01 (10 0 0))\n);\n```\n outOfBounds 的可选项及其含义有：\n + clamp：当实际时间超过 tableFile 里的最大时间（比如上述示例文件里，最大时间为0.01s）以后，保持 tableFile 最大时间定义的那个值（对上述示例文件，为(10 0 0)）不变；\n + repeat：重复前面的变化模式，比如上上述示例中，0 < t < 0.01 时，$U_x=1000\\cdot t$，当 t 超过0.01 时，计算方法为 $U_x = 1000\\cdot (t-0.01)$；\n + error：t > 0.01 时，报错退出；\n + warn：给出警告，但是程序继续运行，边界值计算方法同 clamp。\n\n##### csvFile\n```\ninlet \n{ \n    type uniformFixedValue; \n    uniformValue csvFile; \n    csvFileCoeffs \n    { \n\tfileName \"$FOAM_CASE/velocity.csv\" \n\toutOfBounds clamp; \n\tnHeaderLine 1; \n\tmergeSeparators no; \n\tseparator \",\";\n\trefColumn 0; \n\tcomponentColumns (1 2 3); \n    } \n}\n```\n这种方式是通过一个 `csv` 格式的文件来定义分段函数，文件格式为\n```\ntime,velocity-x,velocity-y,velocity-z\n0,0,0,0\n0.01,10,0,0\n```\n#### 2. flowRateInletVelocity\n这个边界条件指定的是界面上的体积流量或者质量流量，并根据流量来反推速度。速度的方向为**垂直边界并指向区域内部**。一般的用法如下：\n```\n  myPatch\n    {\n        type        flowRateInletVelocity;\n        volumetricFlowRate  0.2; // 体积流量\n        value       uniform (0 0 0);\n    }\n\n\n    myPatch\n    {\n        type                flowRateInletVelocity;\n        massFlowRate        0.2; // 质量流量\n        rho                 rho; //要指定密度场的名字\n        rhoInlet            1.0; //如果上述指定的密度场不存在，则使用这里指定的值作为边界上的密度\n    }\n\n```\n\n类似的，前面 `uniformFixedValue` 边界里的那些类型，`table`， `tableFile`  `csvFile`， `polynomial` 也都可以使用，而且格式是类似的，这里就不重复了，更多细节可以参考[这个页面](http://www.geocities.jp/penguinitis2002/study/OpenFOAM/time_varying_bc.html) \n","source":"_posts/timeVaryingBC1.md","raw":"title: \"两个简单的随时间变化的边界条件\"\ndate: 2015-12-12 14:38:50\ncomments: true\ntags:\n - OpenFOAM\n - Boundary conditions\ncategories:\n - OpenFOAM\n---\n\n这一篇里介绍OpenFOAM自带的两个简单的随时间变化的边界条件： `uniformFixedValue` 和 `flowRateInletVelocity` 。\n\n<!--more-->\n\n#### 1. uniformFixedValue\n这个边界属于第一类边界，即直接指定边界上某个量的值。跟 `fixedValue` 的不同之处在于，这个边界条件允许边界上的值随着时间变化。一般的用法如下：\n\n```\n        type            uniformFixedValue;\n        uniformValue     table\n        (\n            (0.000 (0 0 0.002))\n            (0.010 (0 0 0.002))\n            (0.011 (0.011 0 0.011))\n            (0.100 (0.10 0 0.100))\n        );\n```\n上面这一段指定的是速度 U （也可以是其他矢量，或者标量）的边界条件，通过一个 table 来定义边界随时间的变化行为，具体解释如下（假定时间步长等于0.001 s）：\n\n+ 当 0.00 <= t <= 0.01 s 时，速度值为 (0   0   0.002);\n+ 当 t = 0.011 s 时，速度变为 (0.011   0   0.011);\n+ 0.011 <= t <= 0.100 之间，速度的每个分量均为线性变化的;\n+ 当 t > 0.100 s 以后，速度保持  (0.10   0   0.100) 不变。\n\n除了通过 table 来定义随时间分段线性变化边界条件，还可以定义多项式变化的边界，比如：\n\n```\n            type uniformFixedValue;\n            uniformValue polynomial\n            (\n                ((1 0 0) (0 0 0))\n                ((2 2 0) (1 2 0))\n                ((3 5 0) (2 4 0))\n            );\n```\n以上这段定义的是这样一个边界：x 分量，$U\\_x=1+2\\cdot t+3\\cdot t^2$；y 分量，$U\\_y=2\\cdot t^2+5\\cdot t^4$; z 分量，$U\\_z=0$。\n需要注意的是， `polynomial` 这种方式，目前测试在 2.3.x 以及以下版本不能使用，在最新的 3.0.x 版中可以使用。\n\n此外，还有其他的一些定义方式，列举如下（参考[这个网站](http://www.geocities.jp/penguinitis2002/study/OpenFOAM/time_varying_bc.html)）：\n##### 固定值\n```\ninlet\n{ \n    type uniformFixedValue; \n    uniformValue constant (10 0 0);\n}\n```\n这种方式与 `fixedValue` 效果是一样的。\n##### tableFile\n```\ninlet \n{ \n    type uniformFixedValue; \n    uniformValue tableFile; \n    tableFileCoeffs\n    { \n\tfileName \"$FOAM_CASE/velocity\" \n\toutOfBounds clamp; \n    } \n}\n```\n这个跟 `table` 那种类似，只不过将上面 `table` 的内容写到一个文件里了（这里是 `velocity` 文件）。 `velocity` 文件的格式为\n```\n(\n    (0   (0 0 0))\n    (0.01 (10 0 0))\n);\n```\n outOfBounds 的可选项及其含义有：\n + clamp：当实际时间超过 tableFile 里的最大时间（比如上述示例文件里，最大时间为0.01s）以后，保持 tableFile 最大时间定义的那个值（对上述示例文件，为(10 0 0)）不变；\n + repeat：重复前面的变化模式，比如上上述示例中，0 < t < 0.01 时，$U_x=1000\\cdot t$，当 t 超过0.01 时，计算方法为 $U_x = 1000\\cdot (t-0.01)$；\n + error：t > 0.01 时，报错退出；\n + warn：给出警告，但是程序继续运行，边界值计算方法同 clamp。\n\n##### csvFile\n```\ninlet \n{ \n    type uniformFixedValue; \n    uniformValue csvFile; \n    csvFileCoeffs \n    { \n\tfileName \"$FOAM_CASE/velocity.csv\" \n\toutOfBounds clamp; \n\tnHeaderLine 1; \n\tmergeSeparators no; \n\tseparator \",\";\n\trefColumn 0; \n\tcomponentColumns (1 2 3); \n    } \n}\n```\n这种方式是通过一个 `csv` 格式的文件来定义分段函数，文件格式为\n```\ntime,velocity-x,velocity-y,velocity-z\n0,0,0,0\n0.01,10,0,0\n```\n#### 2. flowRateInletVelocity\n这个边界条件指定的是界面上的体积流量或者质量流量，并根据流量来反推速度。速度的方向为**垂直边界并指向区域内部**。一般的用法如下：\n```\n  myPatch\n    {\n        type        flowRateInletVelocity;\n        volumetricFlowRate  0.2; // 体积流量\n        value       uniform (0 0 0);\n    }\n\n\n    myPatch\n    {\n        type                flowRateInletVelocity;\n        massFlowRate        0.2; // 质量流量\n        rho                 rho; //要指定密度场的名字\n        rhoInlet            1.0; //如果上述指定的密度场不存在，则使用这里指定的值作为边界上的密度\n    }\n\n```\n\n类似的，前面 `uniformFixedValue` 边界里的那些类型，`table`， `tableFile`  `csvFile`， `polynomial` 也都可以使用，而且格式是类似的，这里就不重复了，更多细节可以参考[这个页面](http://www.geocities.jp/penguinitis2002/study/OpenFOAM/time_varying_bc.html) \n","slug":"timeVaryingBC1","published":1,"updated":"2015-12-12T08:41:24.219Z","layout":"post","photos":[],"link":"","_id":"cioiqegbs001bz8mbljlvnyh3"},{"title":"swak4Foam 如何用于名字形如 alpha.water 的场？","date":"2015-11-25T12:33:10.000Z","comments":1,"_content":"\nswak4Foam 的 `groovyBC` 边界条件提供了一种很灵活的用表达式定义边界条件的方法。但是，从 OpenFOAM-2.3 开始，两相流求解器如 `interFoam` 和 `twoPhaseEulerFoam` 开始采用类似于 `alpha.water` 的场，这种场名默认情况下 `groovyBC` 是无法正确识别的，因为 `.` 在 swak4Foam 表达式中有特殊的作用。\n所幸的是，开发人员也早就意识到这个问题了，并给出了解决方案，那就是用 `aliases`。这个东西真是不知道就很难，知道了就很简单，下面举一个我实际用过的例子：\n\n```\nsolid_inlet_left\n    {\n        type               groovyBC;\n        valueExpression    \"-inVel*normal()\"\n        value              uniform (0 0 0);\n        variables (\n            \"A=sum(area());\"\n            \"outFlow{outlet_left}=sum(Uparticles&normal()*area()*alphaparticles);\"\n            \"myFlow=outFlow/alphaparticles;\"\n            \"inVel=myFlow/A;\"\n            );\n        aliases {\n            Uparticles            U.particles;\n            alphaparticles        alpha.particles;\n        }\n    }\n\n```\n\n我想我应该不用再解释什么。\n\n主要参考[这个网页](http://sourceforge.net/p/openfoam-extend/ticketsswak4foam/210/)。\n","source":"_posts/swak4Foam-alpha-water.md","raw":"title: \"swak4Foam 如何用于名字形如 alpha.water 的场？\"\ndate: 2015-11-25 20:33:10\ncomments: true\ntags:\n - OpenFOAM\n - TIL\n - groovyBC\ncategories:\n - swak4Foam\n---\n\nswak4Foam 的 `groovyBC` 边界条件提供了一种很灵活的用表达式定义边界条件的方法。但是，从 OpenFOAM-2.3 开始，两相流求解器如 `interFoam` 和 `twoPhaseEulerFoam` 开始采用类似于 `alpha.water` 的场，这种场名默认情况下 `groovyBC` 是无法正确识别的，因为 `.` 在 swak4Foam 表达式中有特殊的作用。\n所幸的是，开发人员也早就意识到这个问题了，并给出了解决方案，那就是用 `aliases`。这个东西真是不知道就很难，知道了就很简单，下面举一个我实际用过的例子：\n\n```\nsolid_inlet_left\n    {\n        type               groovyBC;\n        valueExpression    \"-inVel*normal()\"\n        value              uniform (0 0 0);\n        variables (\n            \"A=sum(area());\"\n            \"outFlow{outlet_left}=sum(Uparticles&normal()*area()*alphaparticles);\"\n            \"myFlow=outFlow/alphaparticles;\"\n            \"inVel=myFlow/A;\"\n            );\n        aliases {\n            Uparticles            U.particles;\n            alphaparticles        alpha.particles;\n        }\n    }\n\n```\n\n我想我应该不用再解释什么。\n\n主要参考[这个网页](http://sourceforge.net/p/openfoam-extend/ticketsswak4foam/210/)。\n","slug":"swak4Foam-alpha-water","published":1,"updated":"2016-03-14T03:00:46.003Z","layout":"post","photos":[],"link":"","_id":"cioiqegbw001fz8mb79yhg58p"},{"title":"为什么要将声明和定义分离","date":"2016-03-06T12:59:44.000Z","comments":1,"_content":"\nOpenFOAM 中的类基本都遵循类的声明和定义分开在不同文件的规则。具体来说，一般是类的声明放在 \"xxx.H\"，类的成员函数的具体定义 \"xxx.C\"，如果有内联函数(inline)，则还有 \"xxxI.H\"，并且，\"xxx.H\" 文件的最后会有 `#include   \"xxxI.H\" `。这么做不仅是一种代码规范，真正的目的应该是为了防止重复定义的问题。本篇博文用一个简单的例子来说明这个问题。\n\n<!--more-->\n\n\n为了防止混淆，先说明一下概念：\n- 函数声明：即只声明函数的返回类型，函数名以及参数列表，没有函数体，不具体定义函数的功能，比如 \n\n```\nint max(int a, int b);\n```\n- 函数定义：包含函数体,具体定义函数的功能。比如\n\n```\nint max(int a, int b)\n{\n    if (a<b)\n        return b;\n    else \n        return a;\n}\n```\n\n下面用一个简单的例子来说明。\n\n#### 测试一：非内联函数声明和定义必须分开的原因是防止重复定义问题\n##### 正常测试：\n以下的测试代码共包括5个源文件，`inlinetest.H` ， `inlinetest.cpp` ， `inlinederived.H` ， `inlinederived.cpp` ， `main.cpp` ，分别定义如下：\n\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n#endif\n```\n\n+ inlinetest.cpp\n```\n#include \"inlinetest.H\"\n\nItest::Itest(int a, int b):a_(a),b_(b)\n{\n}\nint Itest::b()//非内联函数的定义\n{\n    cout << \"b=\" << b_ << endl;\n    return b_;\n}\n```\n\n+ inlinederived.H\n```\n#ifndef _INLINEDERIVED_H\n#define _INLINEDERIVED_H\n\n#include <iostream>\n#include \"inlinetest.H\"\nusing namespace std;\nclass Iderive: public Itest //派生类\n{\n    public:\n\tint c_;\n\tint c();//非内联成员函数\n\tIderive(int a, int b, int c);\n};\n#endif\n```\n\n+ inlinederived.cpp\n```\n#include \"inlinederived.H\"\n\nIderive::Iderive(int a, int b, int c):Itest(a,b),c_(c)\n{}\nint Iderive::c()\n{\n    cout << \"c=\" << c_ << endl;\n    return c_;\n}\n```\n\n+ main.cpp\n```\n#include <iostream>\n#include \"inlinetest.H\"\n#include \"inlinederived.H\"\n\nint main(int argc, char *argv[])\n{\n    Itest obj1(2,3); //基类对象\n    Iderive obj2(1,2,3); // 派生类对象\n\n    obj1.a();\n    obj1.b();\n    \n    obj2.a();\n    obj2.b();\n    obj2.c();\n\n    return 0;\n}\n```\n以上五个源文件是可以成功编译链接成可执行文件的，运行结果与预期一致：\n```\na=2\nb=3\na=1\nb=2\nc=3\n```\n\n##### 异常测试：\n下面来修改，如果将基类代码改一下，将非内联函数 `b` 的定义也放到头文件里，即：\n\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n\nint Itest::b()//非内联函数的定义\n{\n    cout << \"b=\" << b_ << endl;\n    return b_;\n}\n#endif\n```\n\n+ inlinetest.cpp\n```\n#include \"inlinetest.H\"\n\nItest::Itest(int a, int b):a_(a),b_(b)\n{\n}\n//int Itest::b()//非内联函数的定义\n//{\n//    cout << \"b=\" << b_ << endl;\n//    return b_;\n//}\n```\n\n结果是无法通过编译：\n```\ng++ -c inlinetest.cpp\ng++ -c inlinederived.cpp\ng++ -o main main.o inlinetest.o inlinederived.o\ninlinetest.o:inlinetest.cpp:(.text+0x0): multiple definition of `Itest::b()'\nmain.o:main.cpp:(.text+0x0): first defined here\ninlinederived.o:inlinederived.cpp:(.text+0x0): multiple definition of `Itest::b()'\nmain.o:main.cpp:(.text+0x0): first defined here\nc:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: main.o: bad reloc address 0x0 in section `.ctors'\ncollect2.exe: error: ld returned 1 exit status\nmake: *** [main] Error 1\n```\n可见，编译过程没有出错，但是链接时出错了，报错说 `Itest::b()` 函数（即基类中的非内联成员函数）被重复定义。原因在于，`main.cpp` 里有 `#include \"inlinetest.H\"` ，这句包含了对`Itest::b()` 函数的定义；此外，`main.cpp` 里还有 `#include \"inlinederived.H\"`，而由于 `inlinederived.H` 里，也包含了`#include \"inlinetest.H\"`。所以，相当于`main.cpp` 中也对 `Itest::b()` 函数的定义了两次，于是连接过程就报错了。\n\n在上面可以正常编译的情况里，即将`Itest::b()` 函数放在 `inlinetest.cpp` 里，就不会有这个问题，因为这时头文件里只有函数的声明，而函数的声明是可以重复的。\n\n有人可能会问，这里的 `main.cpp` 里完全可以去掉 `#include \"inlinetest.H\"`，这样也可以解决重复定义问题。对这里的简单例子，是没问题，但是，如果是像 OpenFOAM 这样大的项目，源文件之间的关系非常复杂，那就只能通过将声明和定义分离来解决这个问题了。\n\n注意：这里的内联成员函数 `a` ，声明和定义都在头文件 `inlinetest.H` 里，但是却不会报重复定义的错误。\n\n#### 测试二：内联函数的声明和定义需要在同一个文件里，否则无法通过编译。\n将上面提到的派生类代码修改一下，将内联成员函数的定义放到 `inlinetest.cpp` 里，即\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\n//inline int Itest::a() //内联函数的定义\n//{\n    //cout << \"a=\" << a_ << endl;\n    //return a_;\n//}\n#endif\n```\n\n+ inlinetest.cpp\n```\n#include \"inlinetest.H\"\n\nItest::Itest(int a, int b):a_(a),b_(b)\n{\n}\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n\nint Itest::b()//非内联函数的定义\n{\n    cout << \"b=\" << b_ << endl;\n    return b_;\n}\n```\n这时无法通过编译，编译器报错如下：\n```\ng++ -c main.cpp\nIn file included from main.cpp:2:0:\ninlinetest.H:13:13: warning: inline function 'int Itest::a()' used but never defined [enabled by default]\n  inline int a();\n             ^\ng++ -c inlinetest.cpp\ng++ -c inlinederived.cpp\ng++ -o main main.o inlinetest.o inlinederived.o\nmain.o:main.cpp:(.text+0x5c): undefined reference to `Itest::a()'\nmain.o:main.cpp:(.text+0x70): undefined reference to `Itest::a()'\nc:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: main.o: bad reloc address 0x0 in section `.ctors'\ncollect2.exe: error: ld returned 1 exit status\nmake: *** [main] Error 1\n```\n首先是编译过程有一个警告说 `inlinetest.H` 里的函数 `int Itest::a()` 没有定义，然后链接的时候报了 `undefined reference to 'Itest::a()'`  的错误，说明如果内联函数的声明和定义分属不同源文件，编译器是无法找到函数的定义的。\n\n####  测试三 ：内联函数的定义放到一个单独的源文件，并在头文件里 include 这个源文件\n这个测试我做的比较简单，即将 `inlinetest.H` 拆开，内联函数 `a` 的定义放在单独的一个 `inlinetestI.H` 里：\n\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\n#include \"inlinetestI.H\"\n#endif\n```\n\n+ inlinetestI.H\n```\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n```\n编译链接成功，运行结果跟正常预期一致。\n\n经过上述简单的测试，可以得到以下结论：\n1. 使用C++模板编程时，对于非内联成员函数，函数声明和定义要分开，目的在于防止重复定义的问题。\n2. 内联函数的声明和定义需要在同一个文件里，否则无法通过编译。\n3. OpenFOAM 里将内联成员函数提取出来放到一个单独的文件里（ `*I.H` ），应该只是一种使用惯例。不是 C++ 语法的要求，将 `*I.H` 里的内容拷贝出来放到 `*.H` 后面，然后删除 `#include \"*I.H\"` 效果应该是一样的。\n\n","source":"_posts/separationOfDeclarationAndDefiniton.md","raw":"title: \"为什么要将声明和定义分离\"\ndate: 2016-03-06 20:59:44\ncomments: true\ntags:\n - C++\n - Code Explained\ncategories:\n- C++\n---\n\nOpenFOAM 中的类基本都遵循类的声明和定义分开在不同文件的规则。具体来说，一般是类的声明放在 \"xxx.H\"，类的成员函数的具体定义 \"xxx.C\"，如果有内联函数(inline)，则还有 \"xxxI.H\"，并且，\"xxx.H\" 文件的最后会有 `#include   \"xxxI.H\" `。这么做不仅是一种代码规范，真正的目的应该是为了防止重复定义的问题。本篇博文用一个简单的例子来说明这个问题。\n\n<!--more-->\n\n\n为了防止混淆，先说明一下概念：\n- 函数声明：即只声明函数的返回类型，函数名以及参数列表，没有函数体，不具体定义函数的功能，比如 \n\n```\nint max(int a, int b);\n```\n- 函数定义：包含函数体,具体定义函数的功能。比如\n\n```\nint max(int a, int b)\n{\n    if (a<b)\n        return b;\n    else \n        return a;\n}\n```\n\n下面用一个简单的例子来说明。\n\n#### 测试一：非内联函数声明和定义必须分开的原因是防止重复定义问题\n##### 正常测试：\n以下的测试代码共包括5个源文件，`inlinetest.H` ， `inlinetest.cpp` ， `inlinederived.H` ， `inlinederived.cpp` ， `main.cpp` ，分别定义如下：\n\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n#endif\n```\n\n+ inlinetest.cpp\n```\n#include \"inlinetest.H\"\n\nItest::Itest(int a, int b):a_(a),b_(b)\n{\n}\nint Itest::b()//非内联函数的定义\n{\n    cout << \"b=\" << b_ << endl;\n    return b_;\n}\n```\n\n+ inlinederived.H\n```\n#ifndef _INLINEDERIVED_H\n#define _INLINEDERIVED_H\n\n#include <iostream>\n#include \"inlinetest.H\"\nusing namespace std;\nclass Iderive: public Itest //派生类\n{\n    public:\n\tint c_;\n\tint c();//非内联成员函数\n\tIderive(int a, int b, int c);\n};\n#endif\n```\n\n+ inlinederived.cpp\n```\n#include \"inlinederived.H\"\n\nIderive::Iderive(int a, int b, int c):Itest(a,b),c_(c)\n{}\nint Iderive::c()\n{\n    cout << \"c=\" << c_ << endl;\n    return c_;\n}\n```\n\n+ main.cpp\n```\n#include <iostream>\n#include \"inlinetest.H\"\n#include \"inlinederived.H\"\n\nint main(int argc, char *argv[])\n{\n    Itest obj1(2,3); //基类对象\n    Iderive obj2(1,2,3); // 派生类对象\n\n    obj1.a();\n    obj1.b();\n    \n    obj2.a();\n    obj2.b();\n    obj2.c();\n\n    return 0;\n}\n```\n以上五个源文件是可以成功编译链接成可执行文件的，运行结果与预期一致：\n```\na=2\nb=3\na=1\nb=2\nc=3\n```\n\n##### 异常测试：\n下面来修改，如果将基类代码改一下，将非内联函数 `b` 的定义也放到头文件里，即：\n\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n\nint Itest::b()//非内联函数的定义\n{\n    cout << \"b=\" << b_ << endl;\n    return b_;\n}\n#endif\n```\n\n+ inlinetest.cpp\n```\n#include \"inlinetest.H\"\n\nItest::Itest(int a, int b):a_(a),b_(b)\n{\n}\n//int Itest::b()//非内联函数的定义\n//{\n//    cout << \"b=\" << b_ << endl;\n//    return b_;\n//}\n```\n\n结果是无法通过编译：\n```\ng++ -c inlinetest.cpp\ng++ -c inlinederived.cpp\ng++ -o main main.o inlinetest.o inlinederived.o\ninlinetest.o:inlinetest.cpp:(.text+0x0): multiple definition of `Itest::b()'\nmain.o:main.cpp:(.text+0x0): first defined here\ninlinederived.o:inlinederived.cpp:(.text+0x0): multiple definition of `Itest::b()'\nmain.o:main.cpp:(.text+0x0): first defined here\nc:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: main.o: bad reloc address 0x0 in section `.ctors'\ncollect2.exe: error: ld returned 1 exit status\nmake: *** [main] Error 1\n```\n可见，编译过程没有出错，但是链接时出错了，报错说 `Itest::b()` 函数（即基类中的非内联成员函数）被重复定义。原因在于，`main.cpp` 里有 `#include \"inlinetest.H\"` ，这句包含了对`Itest::b()` 函数的定义；此外，`main.cpp` 里还有 `#include \"inlinederived.H\"`，而由于 `inlinederived.H` 里，也包含了`#include \"inlinetest.H\"`。所以，相当于`main.cpp` 中也对 `Itest::b()` 函数的定义了两次，于是连接过程就报错了。\n\n在上面可以正常编译的情况里，即将`Itest::b()` 函数放在 `inlinetest.cpp` 里，就不会有这个问题，因为这时头文件里只有函数的声明，而函数的声明是可以重复的。\n\n有人可能会问，这里的 `main.cpp` 里完全可以去掉 `#include \"inlinetest.H\"`，这样也可以解决重复定义问题。对这里的简单例子，是没问题，但是，如果是像 OpenFOAM 这样大的项目，源文件之间的关系非常复杂，那就只能通过将声明和定义分离来解决这个问题了。\n\n注意：这里的内联成员函数 `a` ，声明和定义都在头文件 `inlinetest.H` 里，但是却不会报重复定义的错误。\n\n#### 测试二：内联函数的声明和定义需要在同一个文件里，否则无法通过编译。\n将上面提到的派生类代码修改一下，将内联成员函数的定义放到 `inlinetest.cpp` 里，即\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\n//inline int Itest::a() //内联函数的定义\n//{\n    //cout << \"a=\" << a_ << endl;\n    //return a_;\n//}\n#endif\n```\n\n+ inlinetest.cpp\n```\n#include \"inlinetest.H\"\n\nItest::Itest(int a, int b):a_(a),b_(b)\n{\n}\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n\nint Itest::b()//非内联函数的定义\n{\n    cout << \"b=\" << b_ << endl;\n    return b_;\n}\n```\n这时无法通过编译，编译器报错如下：\n```\ng++ -c main.cpp\nIn file included from main.cpp:2:0:\ninlinetest.H:13:13: warning: inline function 'int Itest::a()' used but never defined [enabled by default]\n  inline int a();\n             ^\ng++ -c inlinetest.cpp\ng++ -c inlinederived.cpp\ng++ -o main main.o inlinetest.o inlinederived.o\nmain.o:main.cpp:(.text+0x5c): undefined reference to `Itest::a()'\nmain.o:main.cpp:(.text+0x70): undefined reference to `Itest::a()'\nc:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: main.o: bad reloc address 0x0 in section `.ctors'\ncollect2.exe: error: ld returned 1 exit status\nmake: *** [main] Error 1\n```\n首先是编译过程有一个警告说 `inlinetest.H` 里的函数 `int Itest::a()` 没有定义，然后链接的时候报了 `undefined reference to 'Itest::a()'`  的错误，说明如果内联函数的声明和定义分属不同源文件，编译器是无法找到函数的定义的。\n\n####  测试三 ：内联函数的定义放到一个单独的源文件，并在头文件里 include 这个源文件\n这个测试我做的比较简单，即将 `inlinetest.H` 拆开，内联函数 `a` 的定义放在单独的一个 `inlinetestI.H` 里：\n\n+ inlinetest.H\n```\n#ifndef _INLINETEST_H\n#define _INLINETEST_H\n\n#include <iostream>\nusing namespace std;\n\nclass Itest //基类\n{\n    public:\n\tItest(int a, int b); // 构造函数\n\tint a_, b_; //数据成员\n\tinline int a(); // 内联成员函数\n\tint b(); //非内联成员函数\n};\n#include \"inlinetestI.H\"\n#endif\n```\n\n+ inlinetestI.H\n```\ninline int Itest::a() //内联函数的定义\n{\n    cout << \"a=\" << a_ << endl;\n    return a_;\n}\n```\n编译链接成功，运行结果跟正常预期一致。\n\n经过上述简单的测试，可以得到以下结论：\n1. 使用C++模板编程时，对于非内联成员函数，函数声明和定义要分开，目的在于防止重复定义的问题。\n2. 内联函数的声明和定义需要在同一个文件里，否则无法通过编译。\n3. OpenFOAM 里将内联成员函数提取出来放到一个单独的文件里（ `*I.H` ），应该只是一种使用惯例。不是 C++ 语法的要求，将 `*I.H` 里的内容拷贝出来放到 `*.H` 后面，然后删除 `#include \"*I.H\"` 效果应该是一样的。\n\n","slug":"separationOfDeclarationAndDefiniton","published":1,"updated":"2016-03-14T02:59:46.281Z","layout":"post","photos":[],"link":"","_id":"cioiqegc3001nz8mbify9a45x"},{"title":"ParaView 的远程模式","date":"2015-05-03T12:49:02.000Z","comments":1,"_content":"\nOpenFOAM 用户肯定都用过[ParaView](http://www.paraview.org/)可视化软件，这款软件可以方便地读取 OpenFOAM 的数据进行种类丰富的可视化操作。用 ParaView 可视化 OpenFOAM数据很简单，只需要在算例目录下运行`paraFoam`即可(注一)。但是，如果你不是在本地机器上运行 OpenFOAM，而是在远程机器上运行，这时候想在本地直接用 ParaView 来可视化远程机器上的数据，该怎么办呢？这个问题要分情况讨论：\n1. 远程机器没有禁止远程开启GUI\n  + 本地机器安装的是Linux系统，那么可以这样做：`ssh -X your_remote_machine` 登录到远程机器，注意这里加了`-X`选项，正常的话，应该直接在终端里运行`paraFoam`就可以远程启动 ParaView。\n  + 本地机器安装的是 Windows，这种情况下可以通过安装一个 Xserver，比如[Xming](http://sourceforge.net/projects/xming/)，然后用一个支持 X11 Forwarding 的 SSH 客户端（比如 putty）用`ssh -X`登录到远程机器，正常的话，直接运行`paraFoam`可以远程启动 ParaView。但根据我的使用经验，这种方法稳定性很差，容易崩溃。\n2. 远程机器设置了禁止远程开启GUI程序\n  这种情况下上面提到的两种方法就都不奏效了。这时，下面要介绍的 ParaView 远程模式就能派上用场了。\n\nParaView 远程模式基本的使用方法是，在远程机器上启动一个`pvserver`，然后用本地机器安装的 ParaView 连接到这个 server上(Client/Server 模式)，或者反过来，本地机器开启一个 server，然后让远程机器连接到本地的 server(Client/Server(reverse connection) 模式),以下将一一介绍。\n\n<!--more-->\n\n#### Client/Server 模式\n建立方法如下：\n1. 在远程机器上运行pvserver；\n2. 运行本地机器上的 ParaView，点击菜单栏左上角的“Connect\"，出来如下界面：\n![](/image/paraview_remote/cs1.png)\n3. 选择 \"Add server\"，然后在如下界面中设置 server：\n![](/image/paraview_remote/cs2.png)\n\"Server Type\"选择\"Client/Server\"，\"Host\"填写远程的机器的 IP，\"Port\"默认即可。点\"configure\"，便添加了一个 server，这个设置会自动保存下来，下次再打开 \"Connect\"时，默认就会看到保存的 server 列表。 \n![](/image/paraview_remote/cs3.png)\n4. 双击列表中需要连接的 server，正常地话便连接到远程机器了，这时候点打开文件，看到的便是远程机器上的文件了！\n\n**注意事项**：本地机器和远程机器的必须安装同一个版本的ParaView，否则会报错。此外，这种连接方式不能使用\"PointSprite_Plugin\"插件来显示颗粒。\n\n#### Client/Server(reverse connection) 模式\n有时候，远程机器上无法正常运行`pvserver`, 比如报如下错误：\n```\nWaiting for client… \nConnection URL: cs://A402:11111 \nERROR: In /home/utkarsh/Dashboards/MyTests/NightlyMaster/ParaViewSuperbuild-Release/paraview/src/paraview/VTK/Common/System/vtkSocket.cxx, line 206 \nvtkServerSocket (0x1a1206e0): Socket error in call to bind. Address already in use.\nERROR: In /home/utkarsh/Dashboards/MyTests/NightlyMaster/ParaViewSuperbuild-Release/paraview/src/paraview/ParaViewCore/ClientServerCore/Core/vtkTCPNetworkAccessManager.cxx, line 355 \nvtkTCPNetworkAccessManager (0x1983fa00): Failed to set up server socket.\n Exiting…\n```\n这时，可以采用Client/Server(reverse connection) 模式，这种模式是在本地机器上开启一个 server，然后让远程机器连上它，具体做法如下：\n1. 先在本地机器上启动 ParaView，并按照上面所述的方法建立一个server，注意这里\"Server Type\" 要选择 \"Client/Server(reverse connection)\"，如下图：\n![](/image/paraview_remote/rc1.png)\n2. 连接上这个新建的 server：\n![](/image/paraview_remote/rc2.png)\n连上以后，显示\"waiting for server to connect\"，如下\n![](/image/paraview_remote/rc3.png)\n3. 在远程机器上运行 \n  ```\n    pvserver -rc -ch=192.168.1.34 \n  ```\n 注意这里用选项-rc开启reverse connection 模式，-ch选项指定本地机器的 IP。这样就建立了从远程机器到本地机器的连接。 \n4. 连接建立以后，点打开文件，看到的便是远程机器上的文件了，跟 Client/Server 模式一样。\n\n\n**一个细节值得注意**：当用 Client/Server 模式或Client/Server(reverse connection) 模式建立起本地机器与远程机器的连接以后，\"Save Screenshot\", \"Save Animation\", \"Export Scene\" 以及\"Save state\"会输出到本地机器，而\"Save Data\"和\"Save Geometry\"将仍然输出到远程机器。\n\n\n注一：`paraFoam`的正常运行依赖于 libPV3reader.so 等几个库，有时候这几个库不能正常编译生成。ParaView 自从 4.0 版本以后，不需要使用`paraFoam`直接就能读取 OpenFOAM的数据，用户只需要在算例下新建一个`.foam`结尾的空文件，然后用 ParaView打开这个空文件即可。\n","source":"_posts/paraview-remote.md","raw":"title: \"ParaView 的远程模式\"\ndate: 2015-05-03 20:49:02\ncomments: true\ntags:\n  - paraview\n  - Postprocessing\ncategories:\n  - Paraview\n---\n\nOpenFOAM 用户肯定都用过[ParaView](http://www.paraview.org/)可视化软件，这款软件可以方便地读取 OpenFOAM 的数据进行种类丰富的可视化操作。用 ParaView 可视化 OpenFOAM数据很简单，只需要在算例目录下运行`paraFoam`即可(注一)。但是，如果你不是在本地机器上运行 OpenFOAM，而是在远程机器上运行，这时候想在本地直接用 ParaView 来可视化远程机器上的数据，该怎么办呢？这个问题要分情况讨论：\n1. 远程机器没有禁止远程开启GUI\n  + 本地机器安装的是Linux系统，那么可以这样做：`ssh -X your_remote_machine` 登录到远程机器，注意这里加了`-X`选项，正常的话，应该直接在终端里运行`paraFoam`就可以远程启动 ParaView。\n  + 本地机器安装的是 Windows，这种情况下可以通过安装一个 Xserver，比如[Xming](http://sourceforge.net/projects/xming/)，然后用一个支持 X11 Forwarding 的 SSH 客户端（比如 putty）用`ssh -X`登录到远程机器，正常的话，直接运行`paraFoam`可以远程启动 ParaView。但根据我的使用经验，这种方法稳定性很差，容易崩溃。\n2. 远程机器设置了禁止远程开启GUI程序\n  这种情况下上面提到的两种方法就都不奏效了。这时，下面要介绍的 ParaView 远程模式就能派上用场了。\n\nParaView 远程模式基本的使用方法是，在远程机器上启动一个`pvserver`，然后用本地机器安装的 ParaView 连接到这个 server上(Client/Server 模式)，或者反过来，本地机器开启一个 server，然后让远程机器连接到本地的 server(Client/Server(reverse connection) 模式),以下将一一介绍。\n\n<!--more-->\n\n#### Client/Server 模式\n建立方法如下：\n1. 在远程机器上运行pvserver；\n2. 运行本地机器上的 ParaView，点击菜单栏左上角的“Connect\"，出来如下界面：\n![](/image/paraview_remote/cs1.png)\n3. 选择 \"Add server\"，然后在如下界面中设置 server：\n![](/image/paraview_remote/cs2.png)\n\"Server Type\"选择\"Client/Server\"，\"Host\"填写远程的机器的 IP，\"Port\"默认即可。点\"configure\"，便添加了一个 server，这个设置会自动保存下来，下次再打开 \"Connect\"时，默认就会看到保存的 server 列表。 \n![](/image/paraview_remote/cs3.png)\n4. 双击列表中需要连接的 server，正常地话便连接到远程机器了，这时候点打开文件，看到的便是远程机器上的文件了！\n\n**注意事项**：本地机器和远程机器的必须安装同一个版本的ParaView，否则会报错。此外，这种连接方式不能使用\"PointSprite_Plugin\"插件来显示颗粒。\n\n#### Client/Server(reverse connection) 模式\n有时候，远程机器上无法正常运行`pvserver`, 比如报如下错误：\n```\nWaiting for client… \nConnection URL: cs://A402:11111 \nERROR: In /home/utkarsh/Dashboards/MyTests/NightlyMaster/ParaViewSuperbuild-Release/paraview/src/paraview/VTK/Common/System/vtkSocket.cxx, line 206 \nvtkServerSocket (0x1a1206e0): Socket error in call to bind. Address already in use.\nERROR: In /home/utkarsh/Dashboards/MyTests/NightlyMaster/ParaViewSuperbuild-Release/paraview/src/paraview/ParaViewCore/ClientServerCore/Core/vtkTCPNetworkAccessManager.cxx, line 355 \nvtkTCPNetworkAccessManager (0x1983fa00): Failed to set up server socket.\n Exiting…\n```\n这时，可以采用Client/Server(reverse connection) 模式，这种模式是在本地机器上开启一个 server，然后让远程机器连上它，具体做法如下：\n1. 先在本地机器上启动 ParaView，并按照上面所述的方法建立一个server，注意这里\"Server Type\" 要选择 \"Client/Server(reverse connection)\"，如下图：\n![](/image/paraview_remote/rc1.png)\n2. 连接上这个新建的 server：\n![](/image/paraview_remote/rc2.png)\n连上以后，显示\"waiting for server to connect\"，如下\n![](/image/paraview_remote/rc3.png)\n3. 在远程机器上运行 \n  ```\n    pvserver -rc -ch=192.168.1.34 \n  ```\n 注意这里用选项-rc开启reverse connection 模式，-ch选项指定本地机器的 IP。这样就建立了从远程机器到本地机器的连接。 \n4. 连接建立以后，点打开文件，看到的便是远程机器上的文件了，跟 Client/Server 模式一样。\n\n\n**一个细节值得注意**：当用 Client/Server 模式或Client/Server(reverse connection) 模式建立起本地机器与远程机器的连接以后，\"Save Screenshot\", \"Save Animation\", \"Export Scene\" 以及\"Save state\"会输出到本地机器，而\"Save Data\"和\"Save Geometry\"将仍然输出到远程机器。\n\n\n注一：`paraFoam`的正常运行依赖于 libPV3reader.so 等几个库，有时候这几个库不能正常编译生成。ParaView 自从 4.0 版本以后，不需要使用`paraFoam`直接就能读取 OpenFOAM的数据，用户只需要在算例下新建一个`.foam`结尾的空文件，然后用 ParaView打开这个空文件即可。\n","slug":"paraview-remote","published":1,"updated":"2015-05-03T14:58:20.758Z","layout":"post","photos":[],"link":"","_id":"cioiqegcd001tz8mbbms5fdry"},{"title":"LIGGGHTS tips","date":"2016-05-03T04:38:31.000Z","comments":1,"_content":"\n本篇介绍几个 LIGGGHTS 技巧，read_data，freeze，move，modify_timing，neigh_modify。\n\n<!--more-->\n\nLIGGGHTS 中可以用 STL 格式的几何面来模拟复杂边界的问题。如果想用冻结粒子当作壁面，可以采用如下方法。\n```bash\nread_data test.dat\n\ngroup    Par_wall id <> 1 1000  \nfix       fr Par_wall freeze  \n\n```\n上述代码中，第一行是从外部文件中读取颗粒的信息；第二行是将ID在 1 到 1000 的粒子放到一个 group 里；第三行是将 Par_wall 这个 group 里的粒子冻结起来，具体的操作其实是将这些粒子的力归零，这样粒子将保持最初始的速度。如果将壁面粒子预先生成好，并将其初始速度设置为 0，便可以实现冻结粒子壁面了。\n\ntest.data 文件的数据格式如下，每一列数据的含义见注释：\n```bash\nLAMMPS data file via write_data, version Version LIGGGHTS-PUBLIC 3.2.0, git commit 6de550fbf3b8451f51246aa3c76374012e935340 based on LAMMPS 23 Nov 2013, timestep = 0  ## 第一行随便是什么\n\n5 atoms  ## 颗粒数\n1 atom types ## 颗粒的 type 数\n\n## 模拟区域的大小\n-5.0009999999999999e-01 5.0009999999999999e-01 xlo xhi\n-2.0004000000000002e-01 2.0004000000000002e-01 ylo yhi\n-2.0005500000000001e-01 3.4999999999999998e-01 zlo zhi\n\nAtoms\n#id type diameter density x y z i j k\n1 1 2.9999999999999999e-02 2.5000000000000005e+03 -2.9626205235821884e-01 -1.7191257603378007e-01 -5.2585560979625336e-02 0 0 0\n2 1 2.9999999999999999e-02 2.5000000000000005e+03 -3.1357080694177836e-01 -8.1292507237863978e-02 -3.0941241635135643e-02 0 0 0\n3 1 2.9999999999999999e-02 2.5000000000000005e+03 -3.4986005571676082e-01 -4.6564797686740017e-02 -5.0161637377833301e-02 0 0 0\n4 1 2.9999999999999999e-02 2.5000000000000005e+03 -3.2901105748658366e-01 1.1629149478965480e-01 -2.8537062345934828e-02 0 0 0\n5 1 5.0000000000000003e-02 2.5000000000000000e+03 -3.9692279707164302e-01 1.5000972515153915e-01 -3.5647118241865984e-02 0 0 0\n\n\nVelocities ## 如果粒子的初始速度为零，这一段可以删去。\n#id vx vy vz omegax omegay omegaz\n1 -1.5290519507823870e+00 1.0245516532619933e-01 -1.1594445288149451e+00 6.3791250045904881e+00 2.0674456758001139e+02 1.0276923966595568e+02\n2 -2.1385398568904033e+00 -1.8858415304542153e-01 -9.4897293591801291e-01 1.2732686070189061e+01 1.9114652955524940e+02 -4.8862922016708987e+00\n3 -2.1931823490540205e+00 1.2314081721772643e-01 -1.1305039942880526e+00 -7.7211996358126047e+00 1.8655504536271400e+02 -3.5674698533544941e+01\n4 -2.3661710510727509e+00 6.5301832663338024e-03 -9.2367025774174294e-01 -5.7926985652143115e-01 1.7594397127744105e+02 6.1151183183219171e+00\n5 -2.6032940321288258e+00 1.7791968545582579e-01 -1.0893683893889663e+00 -1.6450273309025711e+01 6.8599979334439681e+01 3.4617478295022179e+00\n```\n\n上述能实现静止的壁面，如果希望用粒子来实现运动壁面（比如旋转），则可以用 move 命令：\n```bash\ngroup rotateWall id <> 1001 2000 # 将 1001 <= id <= 2000 的粒子放到 group rotateWall 里\nfix mov rotateWall move rotate -19.8 0 0 1 0 0 8\n```\nmove 命令有不同的模式，这里用的是 rotate，用这个命令以后，rotateWall 这个 group 里的粒子，将按照指定的参数来进行旋转运动，而不再是根据其受力来更新速度和位置。参数的含义分别为：起始点坐标(x,y,z)；旋转轴的指向(x,y,z)；周期(转一圈的时间)。\n\n最后再介绍几个小 tips：\n1. 有时候想知道程序中哪一部分耗时最多，并据此来优化程序，这时可以在输入脚本的最开头，添加一句 modify_timing on ，之后在程序运行结束后会统计出每一条 fix 命令的耗时信息。\n2. 上述提到的冻结粒子壁面，在使用中会有一个问题：壁面粒子之间的距离通常是很小的，在建立粒子碰撞对的时候，壁面粒子之间通常会形成碰撞对，但壁面粒子之间没必要建立碰撞对，如果壁面粒子很多，这个建立过程是很耗时的。这种情况下，可以通过修改 neigh_modify 命令的参数来防止壁面粒子之间建立碰撞对：\n```bash\nneigh_modify delay 0 exclude group  Par_wall Par_wall\n```\n这条命令将防止在 Par_wall 这个 group 里的粒子彼此之间建立碰撞对。\n","source":"_posts/liggghts-howto.md","raw":"title: \"LIGGGHTS tips\"\ndate: 2016-05-03 12:38:31\ncomments: true\ntags:\n - LIGGGHTS\ncategories:\n - DEM\n---\n\n本篇介绍几个 LIGGGHTS 技巧，read_data，freeze，move，modify_timing，neigh_modify。\n\n<!--more-->\n\nLIGGGHTS 中可以用 STL 格式的几何面来模拟复杂边界的问题。如果想用冻结粒子当作壁面，可以采用如下方法。\n```bash\nread_data test.dat\n\ngroup    Par_wall id <> 1 1000  \nfix       fr Par_wall freeze  \n\n```\n上述代码中，第一行是从外部文件中读取颗粒的信息；第二行是将ID在 1 到 1000 的粒子放到一个 group 里；第三行是将 Par_wall 这个 group 里的粒子冻结起来，具体的操作其实是将这些粒子的力归零，这样粒子将保持最初始的速度。如果将壁面粒子预先生成好，并将其初始速度设置为 0，便可以实现冻结粒子壁面了。\n\ntest.data 文件的数据格式如下，每一列数据的含义见注释：\n```bash\nLAMMPS data file via write_data, version Version LIGGGHTS-PUBLIC 3.2.0, git commit 6de550fbf3b8451f51246aa3c76374012e935340 based on LAMMPS 23 Nov 2013, timestep = 0  ## 第一行随便是什么\n\n5 atoms  ## 颗粒数\n1 atom types ## 颗粒的 type 数\n\n## 模拟区域的大小\n-5.0009999999999999e-01 5.0009999999999999e-01 xlo xhi\n-2.0004000000000002e-01 2.0004000000000002e-01 ylo yhi\n-2.0005500000000001e-01 3.4999999999999998e-01 zlo zhi\n\nAtoms\n#id type diameter density x y z i j k\n1 1 2.9999999999999999e-02 2.5000000000000005e+03 -2.9626205235821884e-01 -1.7191257603378007e-01 -5.2585560979625336e-02 0 0 0\n2 1 2.9999999999999999e-02 2.5000000000000005e+03 -3.1357080694177836e-01 -8.1292507237863978e-02 -3.0941241635135643e-02 0 0 0\n3 1 2.9999999999999999e-02 2.5000000000000005e+03 -3.4986005571676082e-01 -4.6564797686740017e-02 -5.0161637377833301e-02 0 0 0\n4 1 2.9999999999999999e-02 2.5000000000000005e+03 -3.2901105748658366e-01 1.1629149478965480e-01 -2.8537062345934828e-02 0 0 0\n5 1 5.0000000000000003e-02 2.5000000000000000e+03 -3.9692279707164302e-01 1.5000972515153915e-01 -3.5647118241865984e-02 0 0 0\n\n\nVelocities ## 如果粒子的初始速度为零，这一段可以删去。\n#id vx vy vz omegax omegay omegaz\n1 -1.5290519507823870e+00 1.0245516532619933e-01 -1.1594445288149451e+00 6.3791250045904881e+00 2.0674456758001139e+02 1.0276923966595568e+02\n2 -2.1385398568904033e+00 -1.8858415304542153e-01 -9.4897293591801291e-01 1.2732686070189061e+01 1.9114652955524940e+02 -4.8862922016708987e+00\n3 -2.1931823490540205e+00 1.2314081721772643e-01 -1.1305039942880526e+00 -7.7211996358126047e+00 1.8655504536271400e+02 -3.5674698533544941e+01\n4 -2.3661710510727509e+00 6.5301832663338024e-03 -9.2367025774174294e-01 -5.7926985652143115e-01 1.7594397127744105e+02 6.1151183183219171e+00\n5 -2.6032940321288258e+00 1.7791968545582579e-01 -1.0893683893889663e+00 -1.6450273309025711e+01 6.8599979334439681e+01 3.4617478295022179e+00\n```\n\n上述能实现静止的壁面，如果希望用粒子来实现运动壁面（比如旋转），则可以用 move 命令：\n```bash\ngroup rotateWall id <> 1001 2000 # 将 1001 <= id <= 2000 的粒子放到 group rotateWall 里\nfix mov rotateWall move rotate -19.8 0 0 1 0 0 8\n```\nmove 命令有不同的模式，这里用的是 rotate，用这个命令以后，rotateWall 这个 group 里的粒子，将按照指定的参数来进行旋转运动，而不再是根据其受力来更新速度和位置。参数的含义分别为：起始点坐标(x,y,z)；旋转轴的指向(x,y,z)；周期(转一圈的时间)。\n\n最后再介绍几个小 tips：\n1. 有时候想知道程序中哪一部分耗时最多，并据此来优化程序，这时可以在输入脚本的最开头，添加一句 modify_timing on ，之后在程序运行结束后会统计出每一条 fix 命令的耗时信息。\n2. 上述提到的冻结粒子壁面，在使用中会有一个问题：壁面粒子之间的距离通常是很小的，在建立粒子碰撞对的时候，壁面粒子之间通常会形成碰撞对，但壁面粒子之间没必要建立碰撞对，如果壁面粒子很多，这个建立过程是很耗时的。这种情况下，可以通过修改 neigh_modify 命令的参数来防止壁面粒子之间建立碰撞对：\n```bash\nneigh_modify delay 0 exclude group  Par_wall Par_wall\n```\n这条命令将防止在 Par_wall 这个 group 里的粒子彼此之间建立碰撞对。\n","slug":"liggghts-howto","published":1,"updated":"2016-05-25T12:49:30.143Z","_id":"cioiqegcj0020z8mb9wplgdgr","layout":"post","photos":[],"link":""},{"title":"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之 kineticTheoryModel 的子模型","date":"2015-09-19T03:45:35.000Z","comments":1,"_content":"\n上一篇博文解读了 `kineticTheoryModel`  其中提到需要调用子模型来完成其功能，这里将 OpenFOAM 中 `kineticTheoryModel` 模型的子模型罗列如下。\n\n<!--more-->\n\n#### 1. viscosityModel\nviscosityModel 的作用是根据颗粒温度 `Theta` 来计算固相粘度。\n基类代码如下，核心是那个返回固相粘度的 `nu` 函数。 \n```\nnamespace Foam\n{\nnamespace kineticTheoryModels\n{\n\n/*---------------------------------------------------------------------------*\\\n                           Class viscosityModel Declaration\n\\*---------------------------------------------------------------------------*/\n\nclass viscosityModel\n{\n    // Private member functions\n\n        //- Disallow default bitwise copy construct\n        viscosityModel(const viscosityModel&);\n\n        //- Disallow default bitwise assignment\n        void operator=(const viscosityModel&);\nprotected:\n\n    // Protected data\n\n        const dictionary& dict_;\npublic:\n\n    //- Runtime type information\n    TypeName(\"viscosityModel\");\n\n    // Declare runtime constructor selection table\n    declareRunTimeSelectionTable\n    (\n        autoPtr,\n        viscosityModel,\n        dictionary,\n        (\n            const dictionary& dict\n        ),\n        (dict)\n    );\n    // Constructors\n\n        //- Construct from components\n        viscosityModel(const dictionary& dict);\n    // Selectors\n\n        static autoPtr<viscosityModel> New\n        (\n            const dictionary& dict\n        );\n\n    //- Destructor\n    virtual ~viscosityModel();\n\n\n    // Member Functions\n\n        virtual tmp<volScalarField> nu\n        (\n            const volScalarField& alpha1,\n            const volScalarField& Theta,\n            const volScalarField& g0,\n            const volScalarField& rho1,\n            const volScalarField& da,\n            const dimensionedScalar& e\n        ) const = 0;\n\n        virtual bool read()\n        {\n            return true;\n        }\n};\n```\n2.3.x 版自带四种固相粘度模型，分别如下：\n\n##### 1.1 none\n顾名思义，这个模型计算的固相粘度值为零。\n```\n// 注意这里的 kineticTheoryModels 不是类名，而是命名空间\nFoam::tmp<Foam::volScalarField> Foam::kineticTheoryModels::noneViscosity::nu \n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    return dimensionedScalar\n    (\n        \"0\",\n        dimensionSet(0, 2, -1, 0, 0, 0, 0),\n        0.0\n    )*alpha1; // 返回 0\n}\n```\n\n##### 1.2 Syamlal 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::viscosityModels::Syamlal::nu\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return da*sqrt(Theta)*\n    (\n        (4.0/5.0)*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (1.0/15.0)*sqrtPi*g0*(1.0 + e)*(3.0*e - 1.0)*sqr(alpha1)/(3.0 - e)\n      + (1.0/6.0)*alpha1*sqrtPi/(3.0 - e)\n    );\n}\n```\n公式为\n$$\n\\nu_s = d_p \\sqrt{\\Theta}\\left[ \\frac{4}{5}\\varepsilon_s^2 g_0  \\frac{(1+e)}{\\sqrt{\\pi}} + \\frac{1}{15} \\sqrt{\\pi} \\cdot g_0 \\varepsilon_s^2\\frac{(1+e)(3e-1)}{\\sqrt{\\pi}} + \\frac{1}{6}\\varepsilon_s \\frac{\\sqrt{\\pi}}{3-e}\\right ]\n$$\n其中$g_0$是由 径向分布模型计算得到的。\n##### 1.3  HrenyaSinclair 模型\n```\n//- Characteristic length of geometry\n        dimensionedScalar L_; // 新定义的一个变量，\n        \n // 构造函数       \nFoam::kineticTheoryModels::viscosityModels::HrenyaSinclair::HrenyaSinclair\n(\n    const dictionary& dict\n)\n:\n    viscosityModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    L_(\"L\", dimensionSet(0, 1, 0, 0, 0), coeffDict_.lookup(\"L\")) // 从外部读取 L_ 的值\n{}\n\n        \nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::viscosityModels::HrenyaSinclair::nu\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    volScalarField lamda\n    (\n        scalar(1) + da/(6.0*sqrt(2.0)*(alpha1 + scalar(1.0e-5)))/L_\n    );\n\n    return da*sqrt(Theta)*\n    (\n        (4.0/5.0)*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (1.0/15.0)*sqrtPi*g0*(1.0 + e)*(3.0*e - 1)*sqr(alpha1)/(3.0-e)\n      + (1.0/6.0)*sqrtPi*alpha1*(0.5*lamda + 0.25*(3.0*e - 1.0))\n       /(0.5*(3.0 - e)*lamda)\n      + (10/96.0)*sqrtPi/((1.0 + e)*0.5*(3.0 - e)*g0*lamda)\n    );\n}\n```\n公式如下\n$$\n\\begin{aligned} \n\\nu_s = & d_p \\sqrt{\\Theta} \\, [ \\frac{4}{5} \\varepsilon_s \\cdot g_0 \\frac{1+e}{\\sqrt{\\pi}} + \\frac{1}{15}\\sqrt{\\pi} \\cdot g_0 \\varepsilon_s^2 \\frac{(1+e)(3e-1)}{3-e} \\\\\\  \n \\+ & \\frac{1}{6} \\sqrt{\\pi} \\cdot \\frac{0.5\\lambda +  0.25(3e-1)}{0.5(3-e)\\lambda} \\varepsilon_s + \\frac{10}{96}\\sqrt{\\pi}\\cdot \\frac{1}{0.5(1+e)(3-e)g_0\\cdot \\lambda}  ]\n\\end{aligned}\n$$\n其中\n$$\n\\lambda = 1+\\frac{d_p}{6\\sqrt{2}\\cdot \\varepsilon_s}\\cdot \\frac{1}{L}\n$$\n\n\n##### 1.4 Gidaspow 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::viscosityModels::Gidaspow::nu\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return da*sqrt(Theta)*\n    (\n        (4.0/5.0)*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (1.0/15.0)*sqrtPi*g0*(1.0 + e)*sqr(alpha1)\n      + (1.0/6.0)*sqrtPi*alpha1\n      + (10.0/96.0)*sqrtPi/((1.0 + e)*g0)\n    );\n}\n```\n$$\n\\nu_s = d_p\\sqrt{\\Theta}\\left [ \\frac{4}{5} \\varepsilon_s^2 g_0\\cdot \\frac{(1+e)}{\\sqrt{\\pi}}  + \\frac{1}{15} \\sqrt{\\pi}\\cdot g_0(1+e)\\varepsilon_s^2 + \\frac{1}{6} \\sqrt{\\pi}\\cdot \\varepsilon_s^2 + \\frac{10}{96} \\frac{\\sqrt{\\pi}}{(1+e)g_0}\\right ]\n$$\n\n\n\n#### 2. radialModel\n这个类的作用是计算径向分布函数 `g0` \n\n有三种 radialModel 可以选择：\n\n##### 2.1 SinclairJackson\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::SinclairJackson::g0\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return 1.0/(1.0 - cbrt(min(alpha, alphaMinFriction)/alphaMax));\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::SinclairJackson::g0prime\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    volScalarField aByaMax\n    (\n        cbrt(min(max(alpha, scalar(1e-3)), alphaMinFriction)/alphaMax)\n    );\n\n    return (1.0/(3*alphaMax))/sqr(aByaMax - sqr(aByaMax));\n}\n```\n$$\ng\\_0 = \\frac{1}{1-\\sqrt[3]{\\frac{min(\\varepsilon\\_s, \\varepsilon\\_{s,min})}{\\varepsilon\\_{s,max}}}}\n$$\n\n `g0Prime` 为 `g0` 对  `alpha` 的导数 \n\n$$\ng\\_{0Prime} = \\frac{\\partial g\\_0}{\\partial \\varepsilon\\_s} = \\frac{\\frac{1}{3\\cdot \\varepsilon\\_{s,max}}}{(aByaMax-aByaMax^2)^2}\n$$\n\n其中\n\n$$\naByaMax=\\sqrt[3]{\\frac{min(\\varepsilon\\_s,\\varepsilon\\_{s,min})}{\\varepsilon\\_{s,max}}}\n$$\n\n\n##### 2.2 LunSavage\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::LunSavage::g0\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n\n    return pow(1.0 - alpha/alphaMax, -2.5*alphaMax);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::LunSavage::g0prime\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return 2.5*pow(1.0 - alpha/alphaMax, -2.5*alphaMax - 1);\n}\n```\n\n$$\ng\\_0 = \\left( 1-\\frac{\\varepsilon\\_s}{\\varepsilon\\_{s,max}}\\right)^{-2.5\\,\\varepsilon\\_{s,max}}\n$$\n\n##### 2.3 CarnahanStarling 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::CarnahanStarling::g0\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n\n    return\n        1.0/(1.0 - alpha)\n      + 3.0*alpha/(2.0*sqr(1.0 - alpha))\n      + sqr(alpha)/(2.0*pow3(1.0 - alpha));\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::CarnahanStarling::g0prime\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return\n        2.5/sqr(1.0 - alpha)\n      + 4.0*alpha/pow3(1.0 - alpha)\n      + 1.5*sqr(alpha)/pow4(1.0 - alpha);\n}\n```\n\n$$\ng_0 = \\frac{1}{1-\\varepsilon_s} + \\frac{3\\varepsilon_s}{2(1-\\varepsilon_s)^2} + \\frac{\\varepsilon_s^2}{2(1-\\varepsilon_s)^3}\n$$\n\n\n\n\n#### 3. granularPressureModel\n顾名思义，这个类是用来计算固相压力的。\n\nOpenFOAM 内置两种固相压力模型：\n\n##### 3.1 Lun 模型\n`granularPressureCoeff` 返回的是固相压力的系数，这个返回值乘以颗粒温度 `Theta` 才是固相压力。 \n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::Lun::granularPressureCoeff\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n\n    return rho1*alpha1*(1.0 + 2.0*(1.0 + e)*alpha1*g0);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::Lun::\ngranularPressureCoeffPrime\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& g0prime,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n    return rho1*(1.0 + alpha1*(1.0 + e)*(4.0*g0 + 2.0*g0prime*alpha1));\n}\n```\n$$\nP_{s,coeff} = \\rho\\varepsilon_s[1+2(1+e)\\varepsilon_sg_0]\n$$\n\n`granularPressureCoeffPrime` 函数计算的是 $\\partial P_{s,coeff}/\\partial \\varepsilon_s$ 。\n\n##### 3.2 SyamlalRogersOBrien 模型\n\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::SyamlalRogersOBrien::\ngranularPressureCoeff\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n\n    return 2.0*rho1*(1.0 + e)*sqr(alpha1)*g0;\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::SyamlalRogersOBrien::\ngranularPressureCoeffPrime\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& g0prime,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n    return rho1*alpha1*(1.0 + e)*(4.0*g0 + 2.0*g0prime*alpha1);\n}\n```\n$$\nP_{s,coeff} = 2\\rho(1+e)\\varepsilon_s^2\\cdot g_0\n$$\n\n\n\n\n#### 4. frictionalStressModel\n在稠密气固两相流中，当固相体积分率大于某个值时，单纯考虑跟颗粒温度关联的固相压力和固相粘性还不够，还需要考虑所谓的摩擦应力。这个类就是用来计算摩擦应力的。\n\n有两种模型可选：\n\n##### 4.1 Schaeffer 模型\n```\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::Schaeffer\n(\n    const dictionary& dict\n)\n:\n    frictionalStressModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    phi_(\"phi\", dimless, coeffDict_.lookup(\"phi\"))\n{\n    phi_ *= constant::mathematical::pi/180.0; \n    // 这个phi_是一个角度，从外部读取，在外部设置的时候，按角度的单位来设置，这里是将角度转换成弧度。\n}\n\n\n// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //\n\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::~Schaeffer()\n{}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::\nfrictionalPressure\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return\n        dimensionedScalar(\"1e24\", dimensionSet(1, -1, -2, 0, 0), 1e24)\n       *pow(Foam::max(alpha1 - alphaMinFriction, scalar(0)), 10.0);\n}\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::\nfrictionalPressurePrime\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return\n        dimensionedScalar(\"1e25\", dimensionSet(1, -1, -2, 0, 0), 1e25)\n       *pow(Foam::max(alpha1 - alphaMinFriction, scalar(0)), 9.0);\n}\n```\n$$\nP\\_{f} = 10^{24}\\cdot max(\\varepsilon\\_s-\\varepsilon\\_{s,friMin},0)^{10}\n$$\n 同前面一样，`frictionalPressurePrime` 是  `frictionalPressure` 对固相体积分率的导数。\n \n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::nu\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMax,\n    const volScalarField& pf,\n    const volSymmTensorField& D\n) const\n{\n    const scalar I2Dsmall = 1.0e-15;\n\n    // Creating nu assuming it should be 0 on the boundary which may not be\n    // true\n    tmp<volScalarField> tnu\n    (\n        new volScalarField\n        (\n            IOobject\n            (\n                \"Schaeffer:nu\",\n                alpha1.mesh().time().timeName(),\n                alpha1.mesh(),\n                IOobject::NO_READ,\n                IOobject::NO_WRITE,\n                false\n            ),\n            alpha1.mesh(),\n            dimensionedScalar(\"nu\", dimensionSet(0, 2, -1, 0, 0), 0.0)\n        )\n    );\n\n    volScalarField& nuf = tnu();\n\n    forAll (D, celli)\n    {\n        if (alpha1[celli] > alphaMax.value() - 5e-2)\n        {\n            nuf[celli] =\n                0.5*pf[celli]*sin(phi_.value())\n               /(\n                    sqrt(1.0/6.0*(sqr(D[celli].xx() - D[celli].yy())\n                  + sqr(D[celli].yy() - D[celli].zz())\n                  + sqr(D[celli].zz() - D[celli].xx()))\n                  + sqr(D[celli].xy()) + sqr(D[celli].xz())\n                  + sqr(D[celli].yz())) + I2Dsmall\n                );\n        }\n    }\n\n    // Correct coupled BCs\n    nuf.correctBoundaryConditions();\n\n    return tnu;\n}\n```\n\n$$\n\\nu\\_f = \\left \\\\{ \\begin{aligned} \n & 0 ,  &\\varepsilon\\_s \\le \\varepsilon\\_{s,max} \\\\\\\n& \\frac{0.5p\\_f sin\\phi}{\\sqrt{I\\_{2d}}}, & \\varepsilon\\_s \\> \\varepsilon\\_{s,max}\n\\end{aligned} \\right .\n$$\n\n其中，$p\\_f$ 代表的是上面的 `frictionalPressure` \n$$\nD = \\frac{1}{2}(\\nabla U + \\nabla U^T)\n$$\n\n$$\n\\sqrt{I\\_{2D}} = \\frac{1}{6}[(D\\_{11}-D\\_{22})^2 + (D\\_{22}-D\\_{33})^2 + (D\\_{33}-D\\_{11})^2] + D\\_{12}^2 + D\\_{13}^2 + D\\_{23}^2\n$$\n\n\n##### 4.2 JohnsonJackson 模型\n```\nclass JohnsonJackson\n:\n    public frictionalStressModel\n{\n    // Private data\n\n        dictionary coeffDict_;\n\n        //- Material constant for frictional normal stress\n        dimensionedScalar Fr_;\n\n        //- Material constant for frictional normal stress\n        dimensionedScalar eta_;\n\n        //- Material constant for frictional normal stress\n        dimensionedScalar p_;\n\n        //- Angle of internal friction\n        dimensionedScalar phi_;\n        \n        \nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::\nJohnsonJackson\n(\n    const dictionary& dict\n)\n:\n    frictionalStressModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    Fr_(\"Fr\", dimensionSet(1, -1, -2, 0, 0), coeffDict_.lookup(\"Fr\")),\n    eta_(\"eta\", dimless, coeffDict_.lookup(\"eta\")),\n    p_(\"p\", dimless, coeffDict_.lookup(\"p\")),\n    phi_(\"phi\", dimless, coeffDict_.lookup(\"phi\"))\n{\n    phi_ *= constant::mathematical::pi/180.0;\n}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::\nfrictionalPressure\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n\n    return\n        Fr_*pow(max(alpha1 - alphaMinFriction, scalar(0)), eta_)\n       /pow(max(alphaMax - alpha1, scalar(5.0e-2)), p_);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::\nfrictionalPressurePrime\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return Fr_*\n    (\n        eta_*pow(max(alpha1 - alphaMinFriction, scalar(0)), eta_ - 1.0)\n       *(alphaMax-alpha1)\n      + p_*pow(max(alpha1 - alphaMinFriction, scalar(0)), eta_)\n    )/pow(max(alphaMax - alpha1, scalar(5.0e-2)), p_ + 1.0);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::nu\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMax,\n    const volScalarField& pf,\n    const volSymmTensorField& D\n) const\n{\n    return dimensionedScalar(\"0.5\", dimTime, 0.5)*pf*sin(phi_);\n}\n```\n这个模型的特点是需要设置一些跟材料有关的参数。\n$$\nP\\_{f} = F\\_r \\frac{max(\\varepsilon\\_s-\\varepsilon\\_{s,friMin},0)^{\\eta}}{max(\\varepsilon\\_{s,max}-\\varepsilon\\_{s},0)^{p}}\n$$\n\n$$\n\\nu\\_f = 0.5\\cdot p\\_fsin\\phi\n$$\n一些材料的物性参数建议值可参见\"Derivation, Implementation and Validation of Computer Simulation Models for Gas-Solids Fluidized Beds\" Table-3.5。\n\n\n\n\n#### 5. conductivityModel\n这个类的作用是计算 颗粒温度方程中的颗粒温度传导系数，只有在使用偏微分方程求解颗粒温度是才会用到。\n\n \n有三种可选：\n\n##### 5.1 Syamlal 模型\n\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::conductivityModels::Syamlal::kappa\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return rho1*da*sqrt(Theta)*\n    (\n        2.0*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (9.0/8.0)*sqrtPi*g0*0.25*sqr(1.0 + e)*(2.0*e - 1.0)*sqr(alpha1)\n       /(49.0/16.0 - 33.0*e/16.0)\n      + (15.0/32.0)*sqrtPi*alpha1/(49.0/16.0 - 33.0*e/16.0)\n    );\n}\n```\n\n$$\n\\kappa = \\rho d_p \\sqrt{\\Theta}\\left [ 2 \\varepsilon_s^2 g_0 \\frac{1+e}{\\sqrt{\\pi}} + \\frac{\\frac{9}{8} \\sqrt{\\pi}g_0 \\cdot 0.25(1+e)^2(2e-1)\\varepsilon_s^2}{49/16-33e/16} + \\frac{\\frac{15}{32}\\sqrt{\\pi} \\cdot \\varepsilon_s }{49/16-33e/16} \\right ]\n$$\n\n##### 5.2 HrenyaSinclair 模型\n\n```\nFoam::kineticTheoryModels::conductivityModels::HrenyaSinclair::HrenyaSinclair\n(\n    const dictionary& dict\n)\n:\n    conductivityModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    L_(\"L\", dimensionSet(0, 1, 0, 0, 0), coeffDict_.lookup(\"L\"))\n{}\n\n\n// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //\n\nFoam::kineticTheoryModels::conductivityModels::HrenyaSinclair::\n~HrenyaSinclair()\n{}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::conductivityModels::HrenyaSinclair::kappa\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    volScalarField lamda\n    (\n        scalar(1) + da/(6.0*sqrt(2.0)*(alpha1 + scalar(1.0e-5)))/L_\n    );\n\n    return rho1*da*sqrt(Theta)*\n    (\n        2.0*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (9.0/8.0)*sqrtPi*g0*0.25*sqr(1.0 + e)*(2.0*e - 1.0)*sqr(alpha1)\n       /(49.0/16.0 - 33.0*e/16.0)\n      + (15.0/16.0)*sqrtPi*alpha1*(0.5*sqr(e) + 0.25*e - 0.75 + lamda)\n       /((49.0/16.0 - 33.0*e/16.0)*lamda)\n      + (25.0/64.0)*sqrtPi\n       /((1.0 + e)*(49.0/16.0 - 33.0*e/16.0)*lamda*g0)\n    );\n}\n```\n这个模型需要输入一个特征长度 L。\n$$\n\\begin{aligned} \\kappa = \\ & \\rho d_p \\sqrt{\\Theta} \\, [  2\\varepsilon_s^2 g_0 \\frac{1+e}{\\sqrt{\\pi}} + \\frac{\\frac{9}{8} \\sqrt{\\pi}g_0 \\cdot 0.25(1+e)^2(2e-1)\\varepsilon_s^2}{49/16-33e/16} \\\\\\\n\\ + &\\frac{\\frac{15}{16}\\sqrt{\\pi} \\varepsilon_s \\cdot (0.5e^2+0.25e-0.75+\\lambda) }{(49/16-33e/16)\\lambda} + \\frac{\\frac{25}{64}\\sqrt{\\pi}}{(1+e)(49/16-33e/16)\\lambda g_0}\n  ] \\end{aligned}\n$$\n\n其中\n$$\n\\lambda = 1+\\frac{d_p}{6\\sqrt{2}\\varepsilon_s L}\n$$\n\n##### 5.3 Gidaspow 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::conductivityModels::Gidaspow::kappa\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return rho1*da*sqrt(Theta)*\n    (\n        2.0*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (9.0/8.0)*sqrtPi*g0*0.5*(1.0 + e)*sqr(alpha1)\n      + (15.0/16.0)*sqrtPi*alpha1\n      + (25.0/64.0)*sqrtPi/((1.0 + e)*g0)\n    );\n}\n```\n$$\n\\kappa =  \\rho d_p \\sqrt{\\Theta}\\left [  2 \\varepsilon_s^2 g_0 \\frac{1+e}{\\sqrt{\\pi}}  + \\frac{9}{8}\\sqrt{\\pi}g_0\\cdot 0.5(1+e)\\varepsilon_s^2  + \\frac{15}{16}\\sqrt{\\pi} \\varepsilon_s +\\frac{25}{64}\\frac{\\sqrt{\\pi}}{(1+e)g_0}\\right ]\n$$\n\n\n","source":"_posts/kineticTheoryModelSubModels.md","raw":"title: \"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之 kineticTheoryModel 的子模型\"\ndate: 2015-09-19 11:45:35\ncomments: true\ntags:\n - OpenFOAM\n - Code Explained\ncategories:\n - OpenFOAM\n---\n\n上一篇博文解读了 `kineticTheoryModel`  其中提到需要调用子模型来完成其功能，这里将 OpenFOAM 中 `kineticTheoryModel` 模型的子模型罗列如下。\n\n<!--more-->\n\n#### 1. viscosityModel\nviscosityModel 的作用是根据颗粒温度 `Theta` 来计算固相粘度。\n基类代码如下，核心是那个返回固相粘度的 `nu` 函数。 \n```\nnamespace Foam\n{\nnamespace kineticTheoryModels\n{\n\n/*---------------------------------------------------------------------------*\\\n                           Class viscosityModel Declaration\n\\*---------------------------------------------------------------------------*/\n\nclass viscosityModel\n{\n    // Private member functions\n\n        //- Disallow default bitwise copy construct\n        viscosityModel(const viscosityModel&);\n\n        //- Disallow default bitwise assignment\n        void operator=(const viscosityModel&);\nprotected:\n\n    // Protected data\n\n        const dictionary& dict_;\npublic:\n\n    //- Runtime type information\n    TypeName(\"viscosityModel\");\n\n    // Declare runtime constructor selection table\n    declareRunTimeSelectionTable\n    (\n        autoPtr,\n        viscosityModel,\n        dictionary,\n        (\n            const dictionary& dict\n        ),\n        (dict)\n    );\n    // Constructors\n\n        //- Construct from components\n        viscosityModel(const dictionary& dict);\n    // Selectors\n\n        static autoPtr<viscosityModel> New\n        (\n            const dictionary& dict\n        );\n\n    //- Destructor\n    virtual ~viscosityModel();\n\n\n    // Member Functions\n\n        virtual tmp<volScalarField> nu\n        (\n            const volScalarField& alpha1,\n            const volScalarField& Theta,\n            const volScalarField& g0,\n            const volScalarField& rho1,\n            const volScalarField& da,\n            const dimensionedScalar& e\n        ) const = 0;\n\n        virtual bool read()\n        {\n            return true;\n        }\n};\n```\n2.3.x 版自带四种固相粘度模型，分别如下：\n\n##### 1.1 none\n顾名思义，这个模型计算的固相粘度值为零。\n```\n// 注意这里的 kineticTheoryModels 不是类名，而是命名空间\nFoam::tmp<Foam::volScalarField> Foam::kineticTheoryModels::noneViscosity::nu \n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    return dimensionedScalar\n    (\n        \"0\",\n        dimensionSet(0, 2, -1, 0, 0, 0, 0),\n        0.0\n    )*alpha1; // 返回 0\n}\n```\n\n##### 1.2 Syamlal 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::viscosityModels::Syamlal::nu\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return da*sqrt(Theta)*\n    (\n        (4.0/5.0)*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (1.0/15.0)*sqrtPi*g0*(1.0 + e)*(3.0*e - 1.0)*sqr(alpha1)/(3.0 - e)\n      + (1.0/6.0)*alpha1*sqrtPi/(3.0 - e)\n    );\n}\n```\n公式为\n$$\n\\nu_s = d_p \\sqrt{\\Theta}\\left[ \\frac{4}{5}\\varepsilon_s^2 g_0  \\frac{(1+e)}{\\sqrt{\\pi}} + \\frac{1}{15} \\sqrt{\\pi} \\cdot g_0 \\varepsilon_s^2\\frac{(1+e)(3e-1)}{\\sqrt{\\pi}} + \\frac{1}{6}\\varepsilon_s \\frac{\\sqrt{\\pi}}{3-e}\\right ]\n$$\n其中$g_0$是由 径向分布模型计算得到的。\n##### 1.3  HrenyaSinclair 模型\n```\n//- Characteristic length of geometry\n        dimensionedScalar L_; // 新定义的一个变量，\n        \n // 构造函数       \nFoam::kineticTheoryModels::viscosityModels::HrenyaSinclair::HrenyaSinclair\n(\n    const dictionary& dict\n)\n:\n    viscosityModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    L_(\"L\", dimensionSet(0, 1, 0, 0, 0), coeffDict_.lookup(\"L\")) // 从外部读取 L_ 的值\n{}\n\n        \nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::viscosityModels::HrenyaSinclair::nu\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    volScalarField lamda\n    (\n        scalar(1) + da/(6.0*sqrt(2.0)*(alpha1 + scalar(1.0e-5)))/L_\n    );\n\n    return da*sqrt(Theta)*\n    (\n        (4.0/5.0)*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (1.0/15.0)*sqrtPi*g0*(1.0 + e)*(3.0*e - 1)*sqr(alpha1)/(3.0-e)\n      + (1.0/6.0)*sqrtPi*alpha1*(0.5*lamda + 0.25*(3.0*e - 1.0))\n       /(0.5*(3.0 - e)*lamda)\n      + (10/96.0)*sqrtPi/((1.0 + e)*0.5*(3.0 - e)*g0*lamda)\n    );\n}\n```\n公式如下\n$$\n\\begin{aligned} \n\\nu_s = & d_p \\sqrt{\\Theta} \\, [ \\frac{4}{5} \\varepsilon_s \\cdot g_0 \\frac{1+e}{\\sqrt{\\pi}} + \\frac{1}{15}\\sqrt{\\pi} \\cdot g_0 \\varepsilon_s^2 \\frac{(1+e)(3e-1)}{3-e} \\\\\\  \n \\+ & \\frac{1}{6} \\sqrt{\\pi} \\cdot \\frac{0.5\\lambda +  0.25(3e-1)}{0.5(3-e)\\lambda} \\varepsilon_s + \\frac{10}{96}\\sqrt{\\pi}\\cdot \\frac{1}{0.5(1+e)(3-e)g_0\\cdot \\lambda}  ]\n\\end{aligned}\n$$\n其中\n$$\n\\lambda = 1+\\frac{d_p}{6\\sqrt{2}\\cdot \\varepsilon_s}\\cdot \\frac{1}{L}\n$$\n\n\n##### 1.4 Gidaspow 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::viscosityModels::Gidaspow::nu\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return da*sqrt(Theta)*\n    (\n        (4.0/5.0)*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (1.0/15.0)*sqrtPi*g0*(1.0 + e)*sqr(alpha1)\n      + (1.0/6.0)*sqrtPi*alpha1\n      + (10.0/96.0)*sqrtPi/((1.0 + e)*g0)\n    );\n}\n```\n$$\n\\nu_s = d_p\\sqrt{\\Theta}\\left [ \\frac{4}{5} \\varepsilon_s^2 g_0\\cdot \\frac{(1+e)}{\\sqrt{\\pi}}  + \\frac{1}{15} \\sqrt{\\pi}\\cdot g_0(1+e)\\varepsilon_s^2 + \\frac{1}{6} \\sqrt{\\pi}\\cdot \\varepsilon_s^2 + \\frac{10}{96} \\frac{\\sqrt{\\pi}}{(1+e)g_0}\\right ]\n$$\n\n\n\n#### 2. radialModel\n这个类的作用是计算径向分布函数 `g0` \n\n有三种 radialModel 可以选择：\n\n##### 2.1 SinclairJackson\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::SinclairJackson::g0\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return 1.0/(1.0 - cbrt(min(alpha, alphaMinFriction)/alphaMax));\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::SinclairJackson::g0prime\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    volScalarField aByaMax\n    (\n        cbrt(min(max(alpha, scalar(1e-3)), alphaMinFriction)/alphaMax)\n    );\n\n    return (1.0/(3*alphaMax))/sqr(aByaMax - sqr(aByaMax));\n}\n```\n$$\ng\\_0 = \\frac{1}{1-\\sqrt[3]{\\frac{min(\\varepsilon\\_s, \\varepsilon\\_{s,min})}{\\varepsilon\\_{s,max}}}}\n$$\n\n `g0Prime` 为 `g0` 对  `alpha` 的导数 \n\n$$\ng\\_{0Prime} = \\frac{\\partial g\\_0}{\\partial \\varepsilon\\_s} = \\frac{\\frac{1}{3\\cdot \\varepsilon\\_{s,max}}}{(aByaMax-aByaMax^2)^2}\n$$\n\n其中\n\n$$\naByaMax=\\sqrt[3]{\\frac{min(\\varepsilon\\_s,\\varepsilon\\_{s,min})}{\\varepsilon\\_{s,max}}}\n$$\n\n\n##### 2.2 LunSavage\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::LunSavage::g0\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n\n    return pow(1.0 - alpha/alphaMax, -2.5*alphaMax);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::LunSavage::g0prime\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return 2.5*pow(1.0 - alpha/alphaMax, -2.5*alphaMax - 1);\n}\n```\n\n$$\ng\\_0 = \\left( 1-\\frac{\\varepsilon\\_s}{\\varepsilon\\_{s,max}}\\right)^{-2.5\\,\\varepsilon\\_{s,max}}\n$$\n\n##### 2.3 CarnahanStarling 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::CarnahanStarling::g0\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n\n    return\n        1.0/(1.0 - alpha)\n      + 3.0*alpha/(2.0*sqr(1.0 - alpha))\n      + sqr(alpha)/(2.0*pow3(1.0 - alpha));\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::radialModels::CarnahanStarling::g0prime\n(\n    const volScalarField& alpha,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return\n        2.5/sqr(1.0 - alpha)\n      + 4.0*alpha/pow3(1.0 - alpha)\n      + 1.5*sqr(alpha)/pow4(1.0 - alpha);\n}\n```\n\n$$\ng_0 = \\frac{1}{1-\\varepsilon_s} + \\frac{3\\varepsilon_s}{2(1-\\varepsilon_s)^2} + \\frac{\\varepsilon_s^2}{2(1-\\varepsilon_s)^3}\n$$\n\n\n\n\n#### 3. granularPressureModel\n顾名思义，这个类是用来计算固相压力的。\n\nOpenFOAM 内置两种固相压力模型：\n\n##### 3.1 Lun 模型\n`granularPressureCoeff` 返回的是固相压力的系数，这个返回值乘以颗粒温度 `Theta` 才是固相压力。 \n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::Lun::granularPressureCoeff\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n\n    return rho1*alpha1*(1.0 + 2.0*(1.0 + e)*alpha1*g0);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::Lun::\ngranularPressureCoeffPrime\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& g0prime,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n    return rho1*(1.0 + alpha1*(1.0 + e)*(4.0*g0 + 2.0*g0prime*alpha1));\n}\n```\n$$\nP_{s,coeff} = \\rho\\varepsilon_s[1+2(1+e)\\varepsilon_sg_0]\n$$\n\n`granularPressureCoeffPrime` 函数计算的是 $\\partial P_{s,coeff}/\\partial \\varepsilon_s$ 。\n\n##### 3.2 SyamlalRogersOBrien 模型\n\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::SyamlalRogersOBrien::\ngranularPressureCoeff\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n\n    return 2.0*rho1*(1.0 + e)*sqr(alpha1)*g0;\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::granularPressureModels::SyamlalRogersOBrien::\ngranularPressureCoeffPrime\n(\n    const volScalarField& alpha1,\n    const volScalarField& g0,\n    const volScalarField& g0prime,\n    const volScalarField& rho1,\n    const dimensionedScalar& e\n) const\n{\n    return rho1*alpha1*(1.0 + e)*(4.0*g0 + 2.0*g0prime*alpha1);\n}\n```\n$$\nP_{s,coeff} = 2\\rho(1+e)\\varepsilon_s^2\\cdot g_0\n$$\n\n\n\n\n#### 4. frictionalStressModel\n在稠密气固两相流中，当固相体积分率大于某个值时，单纯考虑跟颗粒温度关联的固相压力和固相粘性还不够，还需要考虑所谓的摩擦应力。这个类就是用来计算摩擦应力的。\n\n有两种模型可选：\n\n##### 4.1 Schaeffer 模型\n```\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::Schaeffer\n(\n    const dictionary& dict\n)\n:\n    frictionalStressModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    phi_(\"phi\", dimless, coeffDict_.lookup(\"phi\"))\n{\n    phi_ *= constant::mathematical::pi/180.0; \n    // 这个phi_是一个角度，从外部读取，在外部设置的时候，按角度的单位来设置，这里是将角度转换成弧度。\n}\n\n\n// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //\n\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::~Schaeffer()\n{}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::\nfrictionalPressure\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return\n        dimensionedScalar(\"1e24\", dimensionSet(1, -1, -2, 0, 0), 1e24)\n       *pow(Foam::max(alpha1 - alphaMinFriction, scalar(0)), 10.0);\n}\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::\nfrictionalPressurePrime\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return\n        dimensionedScalar(\"1e25\", dimensionSet(1, -1, -2, 0, 0), 1e25)\n       *pow(Foam::max(alpha1 - alphaMinFriction, scalar(0)), 9.0);\n}\n```\n$$\nP\\_{f} = 10^{24}\\cdot max(\\varepsilon\\_s-\\varepsilon\\_{s,friMin},0)^{10}\n$$\n 同前面一样，`frictionalPressurePrime` 是  `frictionalPressure` 对固相体积分率的导数。\n \n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::Schaeffer::nu\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMax,\n    const volScalarField& pf,\n    const volSymmTensorField& D\n) const\n{\n    const scalar I2Dsmall = 1.0e-15;\n\n    // Creating nu assuming it should be 0 on the boundary which may not be\n    // true\n    tmp<volScalarField> tnu\n    (\n        new volScalarField\n        (\n            IOobject\n            (\n                \"Schaeffer:nu\",\n                alpha1.mesh().time().timeName(),\n                alpha1.mesh(),\n                IOobject::NO_READ,\n                IOobject::NO_WRITE,\n                false\n            ),\n            alpha1.mesh(),\n            dimensionedScalar(\"nu\", dimensionSet(0, 2, -1, 0, 0), 0.0)\n        )\n    );\n\n    volScalarField& nuf = tnu();\n\n    forAll (D, celli)\n    {\n        if (alpha1[celli] > alphaMax.value() - 5e-2)\n        {\n            nuf[celli] =\n                0.5*pf[celli]*sin(phi_.value())\n               /(\n                    sqrt(1.0/6.0*(sqr(D[celli].xx() - D[celli].yy())\n                  + sqr(D[celli].yy() - D[celli].zz())\n                  + sqr(D[celli].zz() - D[celli].xx()))\n                  + sqr(D[celli].xy()) + sqr(D[celli].xz())\n                  + sqr(D[celli].yz())) + I2Dsmall\n                );\n        }\n    }\n\n    // Correct coupled BCs\n    nuf.correctBoundaryConditions();\n\n    return tnu;\n}\n```\n\n$$\n\\nu\\_f = \\left \\\\{ \\begin{aligned} \n & 0 ,  &\\varepsilon\\_s \\le \\varepsilon\\_{s,max} \\\\\\\n& \\frac{0.5p\\_f sin\\phi}{\\sqrt{I\\_{2d}}}, & \\varepsilon\\_s \\> \\varepsilon\\_{s,max}\n\\end{aligned} \\right .\n$$\n\n其中，$p\\_f$ 代表的是上面的 `frictionalPressure` \n$$\nD = \\frac{1}{2}(\\nabla U + \\nabla U^T)\n$$\n\n$$\n\\sqrt{I\\_{2D}} = \\frac{1}{6}[(D\\_{11}-D\\_{22})^2 + (D\\_{22}-D\\_{33})^2 + (D\\_{33}-D\\_{11})^2] + D\\_{12}^2 + D\\_{13}^2 + D\\_{23}^2\n$$\n\n\n##### 4.2 JohnsonJackson 模型\n```\nclass JohnsonJackson\n:\n    public frictionalStressModel\n{\n    // Private data\n\n        dictionary coeffDict_;\n\n        //- Material constant for frictional normal stress\n        dimensionedScalar Fr_;\n\n        //- Material constant for frictional normal stress\n        dimensionedScalar eta_;\n\n        //- Material constant for frictional normal stress\n        dimensionedScalar p_;\n\n        //- Angle of internal friction\n        dimensionedScalar phi_;\n        \n        \nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::\nJohnsonJackson\n(\n    const dictionary& dict\n)\n:\n    frictionalStressModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    Fr_(\"Fr\", dimensionSet(1, -1, -2, 0, 0), coeffDict_.lookup(\"Fr\")),\n    eta_(\"eta\", dimless, coeffDict_.lookup(\"eta\")),\n    p_(\"p\", dimless, coeffDict_.lookup(\"p\")),\n    phi_(\"phi\", dimless, coeffDict_.lookup(\"phi\"))\n{\n    phi_ *= constant::mathematical::pi/180.0;\n}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::\nfrictionalPressure\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n\n    return\n        Fr_*pow(max(alpha1 - alphaMinFriction, scalar(0)), eta_)\n       /pow(max(alphaMax - alpha1, scalar(5.0e-2)), p_);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::\nfrictionalPressurePrime\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMinFriction,\n    const dimensionedScalar& alphaMax\n) const\n{\n    return Fr_*\n    (\n        eta_*pow(max(alpha1 - alphaMinFriction, scalar(0)), eta_ - 1.0)\n       *(alphaMax-alpha1)\n      + p_*pow(max(alpha1 - alphaMinFriction, scalar(0)), eta_)\n    )/pow(max(alphaMax - alpha1, scalar(5.0e-2)), p_ + 1.0);\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::frictionalStressModels::JohnsonJackson::nu\n(\n    const volScalarField& alpha1,\n    const dimensionedScalar& alphaMax,\n    const volScalarField& pf,\n    const volSymmTensorField& D\n) const\n{\n    return dimensionedScalar(\"0.5\", dimTime, 0.5)*pf*sin(phi_);\n}\n```\n这个模型的特点是需要设置一些跟材料有关的参数。\n$$\nP\\_{f} = F\\_r \\frac{max(\\varepsilon\\_s-\\varepsilon\\_{s,friMin},0)^{\\eta}}{max(\\varepsilon\\_{s,max}-\\varepsilon\\_{s},0)^{p}}\n$$\n\n$$\n\\nu\\_f = 0.5\\cdot p\\_fsin\\phi\n$$\n一些材料的物性参数建议值可参见\"Derivation, Implementation and Validation of Computer Simulation Models for Gas-Solids Fluidized Beds\" Table-3.5。\n\n\n\n\n#### 5. conductivityModel\n这个类的作用是计算 颗粒温度方程中的颗粒温度传导系数，只有在使用偏微分方程求解颗粒温度是才会用到。\n\n \n有三种可选：\n\n##### 5.1 Syamlal 模型\n\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::conductivityModels::Syamlal::kappa\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return rho1*da*sqrt(Theta)*\n    (\n        2.0*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (9.0/8.0)*sqrtPi*g0*0.25*sqr(1.0 + e)*(2.0*e - 1.0)*sqr(alpha1)\n       /(49.0/16.0 - 33.0*e/16.0)\n      + (15.0/32.0)*sqrtPi*alpha1/(49.0/16.0 - 33.0*e/16.0)\n    );\n}\n```\n\n$$\n\\kappa = \\rho d_p \\sqrt{\\Theta}\\left [ 2 \\varepsilon_s^2 g_0 \\frac{1+e}{\\sqrt{\\pi}} + \\frac{\\frac{9}{8} \\sqrt{\\pi}g_0 \\cdot 0.25(1+e)^2(2e-1)\\varepsilon_s^2}{49/16-33e/16} + \\frac{\\frac{15}{32}\\sqrt{\\pi} \\cdot \\varepsilon_s }{49/16-33e/16} \\right ]\n$$\n\n##### 5.2 HrenyaSinclair 模型\n\n```\nFoam::kineticTheoryModels::conductivityModels::HrenyaSinclair::HrenyaSinclair\n(\n    const dictionary& dict\n)\n:\n    conductivityModel(dict),\n    coeffDict_(dict.subDict(typeName + \"Coeffs\")),\n    L_(\"L\", dimensionSet(0, 1, 0, 0, 0), coeffDict_.lookup(\"L\"))\n{}\n\n\n// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //\n\nFoam::kineticTheoryModels::conductivityModels::HrenyaSinclair::\n~HrenyaSinclair()\n{}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::conductivityModels::HrenyaSinclair::kappa\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    volScalarField lamda\n    (\n        scalar(1) + da/(6.0*sqrt(2.0)*(alpha1 + scalar(1.0e-5)))/L_\n    );\n\n    return rho1*da*sqrt(Theta)*\n    (\n        2.0*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (9.0/8.0)*sqrtPi*g0*0.25*sqr(1.0 + e)*(2.0*e - 1.0)*sqr(alpha1)\n       /(49.0/16.0 - 33.0*e/16.0)\n      + (15.0/16.0)*sqrtPi*alpha1*(0.5*sqr(e) + 0.25*e - 0.75 + lamda)\n       /((49.0/16.0 - 33.0*e/16.0)*lamda)\n      + (25.0/64.0)*sqrtPi\n       /((1.0 + e)*(49.0/16.0 - 33.0*e/16.0)*lamda*g0)\n    );\n}\n```\n这个模型需要输入一个特征长度 L。\n$$\n\\begin{aligned} \\kappa = \\ & \\rho d_p \\sqrt{\\Theta} \\, [  2\\varepsilon_s^2 g_0 \\frac{1+e}{\\sqrt{\\pi}} + \\frac{\\frac{9}{8} \\sqrt{\\pi}g_0 \\cdot 0.25(1+e)^2(2e-1)\\varepsilon_s^2}{49/16-33e/16} \\\\\\\n\\ + &\\frac{\\frac{15}{16}\\sqrt{\\pi} \\varepsilon_s \\cdot (0.5e^2+0.25e-0.75+\\lambda) }{(49/16-33e/16)\\lambda} + \\frac{\\frac{25}{64}\\sqrt{\\pi}}{(1+e)(49/16-33e/16)\\lambda g_0}\n  ] \\end{aligned}\n$$\n\n其中\n$$\n\\lambda = 1+\\frac{d_p}{6\\sqrt{2}\\varepsilon_s L}\n$$\n\n##### 5.3 Gidaspow 模型\n```\nFoam::tmp<Foam::volScalarField>\nFoam::kineticTheoryModels::conductivityModels::Gidaspow::kappa\n(\n    const volScalarField& alpha1,\n    const volScalarField& Theta,\n    const volScalarField& g0,\n    const volScalarField& rho1,\n    const volScalarField& da,\n    const dimensionedScalar& e\n) const\n{\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n\n    return rho1*da*sqrt(Theta)*\n    (\n        2.0*sqr(alpha1)*g0*(1.0 + e)/sqrtPi\n      + (9.0/8.0)*sqrtPi*g0*0.5*(1.0 + e)*sqr(alpha1)\n      + (15.0/16.0)*sqrtPi*alpha1\n      + (25.0/64.0)*sqrtPi/((1.0 + e)*g0)\n    );\n}\n```\n$$\n\\kappa =  \\rho d_p \\sqrt{\\Theta}\\left [  2 \\varepsilon_s^2 g_0 \\frac{1+e}{\\sqrt{\\pi}}  + \\frac{9}{8}\\sqrt{\\pi}g_0\\cdot 0.5(1+e)\\varepsilon_s^2  + \\frac{15}{16}\\sqrt{\\pi} \\varepsilon_s +\\frac{25}{64}\\frac{\\sqrt{\\pi}}{(1+e)g_0}\\right ]\n$$\n\n\n","slug":"kineticTheoryModelSubModels","published":1,"updated":"2015-11-26T13:38:11.789Z","layout":"post","photos":[],"link":"","_id":"cioiqegcq0025z8mbfx0zc315"},{"title":"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之 kineticTheoryModel","date":"2015-09-19T02:26:09.000Z","comments":1,"_content":"\nOpenFOAM 中双流体模型的 kineticTheoryModel 是以 \" *Derivation, implementation, and validation of computer simulation models for gas-solid fluidized beds*, B.G.M. van Wachem, Ph.D. Thesis, Delft University of Technology, Amsterdam, 2000. \" 为蓝本来设计的，下面分析这个类的代码。需要注意的是，这个类需要调用一些别的类（如 viscosityModel 等，后面会一一分析）来完成其功能。\n\n<!--more-->\n\n\n\n\n\n#### 1. 头文件 kineticTheoryModel.H  \n\n头文件中要注意的是 kineticTheoryModel 类的继承关系，以及六个子类的智能指针作为 kineticTheoryModel 类的数据成员。\n\n```\nclass kineticTheoryModel\n:\n    public eddyViscosity\n    <\n        RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >\n    > // 继承自湍流类，所以这里 kineticTheoryModel 跟湍流类使用同样的接口\n{\n    // Private data\n\n        // Input Fields\n            const phaseModel& phase_;\n            \n        // Sub-models \n\t// 下面五个 sub models 是kineticTheoryModel类为了实现其功能需要调用的类。\n\t//子类的智能指针定义为当前类的数据成员。注意这里的 \"kineticTheoryModels\" 是 namespace。\n        \n            //- Run-time selected viscosity model\n            autoPtr<kineticTheoryModels::viscosityModel> viscosityModel_;\n            \n            //- Run-time selected conductivity model\n            autoPtr<kineticTheoryModels::conductivityModel> conductivityModel_;\n            //- Run-time selected radial distribution model\n            autoPtr<kineticTheoryModels::radialModel> radialModel_;\n\n            //- Run-time selected granular pressure model\n            autoPtr<kineticTheoryModels::granularPressureModel>\n                granularPressureModel_;\n\n            //- Run-time selected frictional stress model\n            autoPtr<kineticTheoryModels::frictionalStressModel>\n                frictionalStressModel_;\n\n  ......\n  ......\n\n```\n\n#### 2. 构造函数\n\n注意这里向基类传递的参数，这里的湍流类的继承关系比较复杂，比单相湍流复杂很多，后面会有具体的分析\n```\nFoam::RASModels::kineticTheoryModel::kineticTheoryModel\n(\n    const volScalarField& alpha,\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& phase,\n    const word& propertiesName,\n    const word& type\n)\n:\n    eddyViscosity<RASModel<PhaseCompressibleTurbulenceModel<phaseModel> > >\n    (\n        type,\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        phase,\n        propertiesName\n    ),\n\n    phase_(phase),\n\n    viscosityModel_\n    (\n        kineticTheoryModels::viscosityModel::New\n        (\n            this->coeffDict_ // coeffDict_ 是 RASModel的成员\n        )\n    ),\n    conductivityModel_\n    (\n        kineticTheoryModels::conductivityModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n    radialModel_\n    (\n        kineticTheoryModels::radialModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n    granularPressureModel_\n    (\n        kineticTheoryModels::granularPressureModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n    frictionalStressModel_\n    (\n        kineticTheoryModels::frictionalStressModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n\n......\n......\n```\n\n#### 3. 主要成员函数\n```\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::k() const\n{\n    // notImplemented 是 Error.H中定义的一个函数，用于提示某个模型或函数没有实现。\n    notImplemented(\"kineticTheoryModel::k()\"); \n    return nut_;\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::epsilon() const\n{\n    notImplemented(\"kineticTheoryModel::epsilon()\");\n    return nut_;\n}\n\n```\n `k` 和  `epsilon` 是两个从基类继承下来的函数，这两个函数在基类中是纯虚函数，所以虽然 kineticTheoryModel 用不到它们，但是还是需要进行定义，否则 kineticTheoryModel 类就将是一个虚基类，从而无法创建对象了。\n\n\n```\nFoam::tmp<Foam::volSymmTensorField>\nFoam::RASModels::kineticTheoryModel::R() const\n{\n    return tmp<volSymmTensorField>\n    (\n        new volSymmTensorField\n        (\n            IOobject\n            (\n                IOobject::groupName(\"R\", this->U_.group()),\n                this->runTime_.timeName(),\n                this->mesh_,\n                IOobject::NO_READ,\n                IOobject::NO_WRITE\n            ),\n          - (this->nut_)*dev(twoSymm(fvc::grad(this->U_)))\n          - (lambda_*fvc::div(this->phi_))*symmTensor::I\n        )\n    );\n}\n\n```\nR 函数返回所谓的雷诺应力，其实跟单相湍流类的 R 函数返回值有点差别：\n$$\n-\\nu_t[\\nabla U + \\nabla U^T-\\frac{2}{3}(\\nabla \\cdot U)\\cdot \\mathrm{I}] - \\lambda(\\nabla \\cdot U)\\cdot \\mathrm{I}\n$$\n这里的 R 与下面的 devRoReff 函数返回值只相差一个 rho。\n而单相湍流模型的 R 则为：\n$$\n\\frac{2}{3}k\\cdot \\mathrm{I}- \\nu_t (\\nabla U + \\nabla U^T)\n$$\n\n```\n\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::pPrime() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n\n\nFoam::tmp<Foam::surfaceScalarField>\nFoam::RASModels::kineticTheoryModel::pPrimef() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return fvc::interpolate\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n```\npPrime 和 pPrimef 两个函数，返回的是固相压力对固相孔隙率的导数（$\\partial P_s/\\partial \\varepsilon_s$）。\n\n\n\n两个在\"UEqn.H\" 被动量方程调用的函数\n```\nFoam::tmp<Foam::volSymmTensorField>\nFoam::RASModels::kineticTheoryModel::devRhoReff() const\n{\n    return tmp<volSymmTensorField>\n    (\n        new volSymmTensorField\n        (\n            IOobject\n            (\n                IOobject::groupName(\"devRhoReff\", this->U_.group()),\n                this->runTime_.timeName(),\n                this->mesh_,\n                IOobject::NO_READ,\n                IOobject::NO_WRITE\n            ),\n          - (this->rho_*this->nut_)\n           *dev(twoSymm(fvc::grad(this->U_)))\n          - ((this->rho_*lambda_)*fvc::div(this->phi_))*symmTensor::I\n        )\n    );\n}\n\n\nFoam::tmp<Foam::fvVectorMatrix>\nFoam::RASModels::kineticTheoryModel::divDevRhoReff\n(\n    volVectorField& U\n) const\n{\n    return\n    (\n      - fvm::laplacian(this->rho_*this->nut_, U)\n      - fvc::div\n        (\n            (this->rho_*this->nut_)*dev2(T(fvc::grad(U)))\n          + ((this->rho_*lambda_)*fvc::div(this->phi_))\n           *dimensioned<symmTensor>(\"I\", dimless, symmTensor::I)\n        )\n    );\n}\n```\n对应的公式分别为：\n**devRhoReff**\n$$\n\\- \\rho  \\nu_t \\cdot dev(\\nabla U + \\nabla U^T) - \\rho \\lambda(\\nabla \\cdot U)\\cdot  \\mathrm{I} \\\\\\\\\n = - \\rho  \\nu_t(\\nabla U + \\nabla U^T) +  \\rho  \\nu_t \\frac{2}{3}(\\nabla \\cdot U)\\cdot \\mathrm{I} - \\rho \\lambda (\\nabla \\cdot U)\\cdot  \\mathrm{I}\n$$\n\n**divDevRhoReff**\n$$\n\\ - \\nabla \\cdot (\\rho \\nu_t \\nabla U) - \\nabla \\cdot \\left [\\rho \\nu_t \\nabla U^T\\ - \\rho \\nu_t \\frac{2}{3}(\\nabla \\cdot U)\\cdot \\mathrm{I} \\right ] + \\rho \\lambda (\\nabla \\cdot U) \\cdot \\mathrm{I} )\n$$\n\n\n `correct`是计算颗粒温度 `Theta` 的函数，这是 kineticTheoryModel 类最重要的部分。\n```\nvoid Foam::RASModels::kineticTheoryModel::correct()\n{\n    // Local references\n    volScalarField alpha(max(this->alpha_, scalar(0)));\n    const volScalarField& rho = phase_.rho();\n    const surfaceScalarField& alphaRhoPhi = this->alphaRhoPhi_;\n    const volVectorField& U = this->U_; // 当前相的速度\n    const volVectorField& Uc_ = phase_.fluid().otherPhase(phase_).U(); // 另一相的速度\n\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n    dimensionedScalar ThetaSmall(\"ThetaSmall\", Theta_.dimensions(), 1.0e-6);\n    dimensionedScalar ThetaSmallSqrt(sqrt(ThetaSmall));\n\n    tmp<volScalarField> tda(phase_.d()); // 颗粒直径\n    const volScalarField& da = tda();\n\n    tmp<volTensorField> tgradU(fvc::grad(this->U_));\n    const volTensorField& gradU(tgradU());\n    volSymmTensorField D(symm(gradU));\n\n    // Calculating the radial distribution function \n    // 调用 radialModel 类 来计算径向分布函数\n    gs0_ = radialModel_->g0(alpha, alphaMinFriction_, alphaMax_);\n\n    if (!equilibrium_) // 如果 equilibrium_ = off ，那么将用这个偏微分方程来计算颗粒温度，否则将改用一个代数方程，见下文\n    {\n        // Particle viscosity (Table 3.2, p.47)\n\t// 调用viscosityModel 来更新颗粒相的粘度\n        nut_ = viscosityModel_->nu(alpha, Theta_, gs0_, rho, da, e_);\n\n        volScalarField ThetaSqrt(sqrt(Theta_));\n\n        // Bulk viscosity  p. 45 (Lun et al. 1984). \n        lambda_ = (4.0/3.0)*sqr(alpha)*da*gs0_*(1.0 + e_)*ThetaSqrt/sqrtPi;\n\n        // Stress tensor, Definitions, Table 3.1, p. 43\n        volSymmTensorField tau // 颗粒应力\n        (\n            rho*(2.0*nut_*D + (lambda_ - (2.0/3.0)*nut_)*tr(D)*I)\n        );\n\n        // Dissipation (Eq. 3.24, p.50)\n        volScalarField gammaCoeff //颗粒动能耗散项\n        (\n            12.0*(1.0 - sqr(e_))\n           *max(sqr(alpha), residualAlpha_)\n           *rho*gs0_*(1.0/da)*ThetaSqrt/sqrtPi\n        );\n\n        // Drag // 调用曳力模型计算曳力系数\n        volScalarField beta(phase_.fluid().drag(phase_).K());\n\n        // Eq. 3.25, p. 50 Js = J1 - J2\n        volScalarField J1(3.0*beta);\n        volScalarField J2\n        (\n            0.25*sqr(beta)*da*magSqr(U - Uc_)\n           /(\n               max(alpha, residualAlpha_)*rho\n              *sqrtPi*(ThetaSqrt + ThetaSmallSqrt)\n            )\n        );\n\n        // particle pressure - coefficient in front of Theta (Eq. 3.22, p. 45)\n        volScalarField PsCoeff\n        (\n            granularPressureModel_->granularPressureCoeff\n            (\n                alpha,\n                gs0_,\n                rho,\n                e_\n            )\n        );\n\n        // 'thermal' conductivity (Table 3.3, p. 49)\n\t// 调用 conductivityModel 计算颗粒脉动能量的传导系数\n        kappa_ = conductivityModel_->kappa(alpha, Theta_, gs0_, rho, da, e_);\n\n        // Construct the granular temperature equation (Eq. 3.20, p. 44)\n        // NB. note that there are two typos in Eq. 3.20:\n        //     Ps should be without grad\n        //     the laplacian has the wrong sign\n\t// 构建颗粒温度方程，注意，开头提到的文献里的颗粒温度方程有两处 typo，\n\t//下面的代码修复了这两处错误，后文会给出正确的公式。\n        fvScalarMatrix ThetaEqn\n        (\n            1.5*\n            (\n                fvm::ddt(alpha, rho, Theta_)\n              + fvm::div(alphaRhoPhi, Theta_)\n              - fvc::Sp(fvc::ddt(alpha, rho) + fvc::div(alphaRhoPhi), Theta_)\n            )\n          - fvm::laplacian(kappa_, Theta_, \"laplacian(kappa,Theta)\")\n         ==\n            fvm::SuSp(-((PsCoeff*I) && gradU), Theta_)\n          + (tau && gradU)\n          + fvm::Sp(-gammaCoeff, Theta_)\n          + fvm::Sp(-J1, Theta_)\n          + fvm::Sp(J2/(Theta_ + ThetaSmall), Theta_)\n        );\n\n        ThetaEqn.relax();\n        ThetaEqn.solve();\n    }\n    else // 如果 equilibrium = on， 将使用一个代数方程来计算颗粒温度。\n    {\n        // Equilibrium => dissipation == production\n        // Eq. 4.14, p.82\n        volScalarField K1(2.0*(1.0 + e_)*rho*gs0_);\n        volScalarField K3\n        (\n            0.5*da*rho*\n            (\n                (sqrtPi/(3.0*(3.0 - e_)))\n               *(1.0 + 0.4*(1.0 + e_)*(3.0*e_ - 1.0)*alpha*gs0_)\n               +1.6*alpha*gs0_*(1.0 + e_)/sqrtPi\n            )\n        );\n\n        volScalarField K2\n        (\n            4.0*da*rho*(1.0 + e_)*alpha*gs0_/(3.0*sqrtPi) - 2.0*K3/3.0\n        );\n\n        volScalarField K4(12.0*(1.0 - sqr(e_))*rho*gs0_/(da*sqrtPi));\n\n        volScalarField trD\n        (\n            alpha/(alpha + residualAlpha_)\n           *fvc::div(this->phi_)\n        );\n        volScalarField tr2D(sqr(trD));\n        volScalarField trD2(tr(D & D));\n\n        volScalarField t1(K1*alpha + rho);\n        volScalarField l1(-t1*trD);\n        volScalarField l2(sqr(t1)*tr2D);\n        volScalarField l3\n        (\n            4.0\n           *K4\n           *alpha\n           *(2.0*K3*trD2 + K2*tr2D)\n        );\n\n        Theta_ = sqr\n        (\n            (l1 + sqrt(l2 + l3))\n           /(2.0*max(alpha, residualAlpha_)*K4)\n        );\n\n        kappa_ = conductivityModel_->kappa(alpha, Theta_, gs0_, rho, da, e_);\n    }\n\n\n   // 限定 颗粒温度的上下限。\n  // max 和 min 函数的定义，没有找到。经验证， max 的作用是让小于0的归零， min 是让大于100的等于100。\n    Theta_.max(0);\n    Theta_.min(100);\n\n    {\n\t//利用先得到的颗粒温度更新颗粒相的粘度\n        // particle viscosity (Table 3.2, p.47)\n        nut_ = viscosityModel_->nu(alpha, Theta_, gs0_, rho, da, e_);\n\n        volScalarField ThetaSqrt(sqrt(Theta_));\n\n        // Bulk viscosity  p. 45 (Lun et al. 1984).\n        lambda_ = (4.0/3.0)*sqr(alpha)*da*gs0_*(1.0 + e_)*ThetaSqrt/sqrtPi;\n\n        // Frictional pressure // 计算由于颗粒之间的摩擦作用产生的一个等效的颗粒相压力作用。\n        volScalarField pf\n        (\n            frictionalStressModel_->frictionalPressure\n            (\n                alpha,\n                alphaMinFriction_,\n                alphaMax_\n            )\n        );\n\n        // Add frictional shear viscosity, Eq. 3.30, p. 52\n       // 将颗粒摩擦产生的颗粒相粘度加到由颗粒温度计算得到的颗粒相粘度中，作为总的颗粒相粘度\n        nut_ += frictionalStressModel_->nu \n        (\n            alpha,\n            alphaMax_,\n            pf/rho,\n            D\n        );\n\n        // Limit viscosity\n\t// 限定颗粒相粘度的上限\n        nut_.min(100);\n    }\n\n    if (debug)\n    {\n        Info<< typeName << ':' << nl\n            << \"    max(Theta) = \" << max(Theta_).value() << nl\n            << \"    max(nut) = \" << max(nut_).value() << endl;\n    }\n}\n```\n\n这里重点关注一下 `ThetaEqn` 的写法。\n```\nfvScalarMatrix ThetaEqn\n  (\n      1.5*\n      (\n          fvm::ddt(alpha, rho, Theta_)\n        + fvm::div(alphaRhoPhi, Theta_)\n        - fvc::Sp(fvc::ddt(alpha, rho) + fvc::div(alphaRhoPhi), Theta_)\n      )\n    - fvm::laplacian(kappa_, Theta_, \"laplacian(kappa,Theta)\")\n   ==\n      fvm::SuSp(-((PsCoeff*I) && gradU), Theta_)\n    + (tau && gradU)\n    + fvm::Sp(-gammaCoeff, Theta_)\n    + fvm::Sp(-J1, Theta_)\n    + fvm::Sp(J2/(Theta_ + ThetaSmall), Theta_)\n );\n```\n对应的偏微分方程是，\n$$\n\\frac{3}{2}\\left [ \\frac{\\partial }{\\partial t}(\\varepsilon_s \\rho_s \\Theta)+ \\nabla \\cdot (\\varepsilon_s \\rho_s \\Theta U_s) \\right ] = (-P_s \\mathrm{I} + \\tau_s):\\nabla U_s + \\nabla \\cdot (\\kappa_s \\nabla \\Theta) - \\gamma_s - J_s\n$$\n下面将公式与代码一一对应。代码里跟公式相比，多了一项\n$$\n-\\Theta\\frac{\\partial }{\\partial t}(\\varepsilon_s \\rho_s) - \\Theta \\nabla \\cdot (\\varepsilon_s \\rho_s U_s)\n$$\n这样，相当于代码对应的前两项是\n$$\n\\varepsilon_s \\rho_s \\frac{\\partial \\Theta}{\\partial t} + \\varepsilon_s \\rho_s U_s \\cdot \\nabla \\Theta\n$$\n这样做的目的仍不是很明确。\n\nLaplacian 项不需多言，剩下的几项，全都当作了源项来处理。\n$(-P\\_s \\mathrm{I} + \\tau\\_s)\\:\\nabla U\\_s$ 拆开成了两项，分别对应 `fvm::SuSp(-((PsCoeff*I) && gradU), Theta_)` 和 `(tau && gradU)` 。第一项，由于$P_s$是$\\Theta$ 的函数，所以，在固相压力类中，返回的值是固相压力系数（固相压力除以颗粒温度），在这里将$\\Theta$进行了隐式处理。而由于$\\tau$与$\\nabla U_s$与$\\Theta$无关，所以就只当成一般的源项了。\n\n$\\gamma$项的处理与颗粒压力类似，也是只定义 `gammaCoeff` ，然后将$\\Theta$作隐式处理。由于实际上$\\gamma$的表达式\n$$\n\\gamma = \\frac{12(1-e^2)\\varepsilon\\_s^2\\rho g\\_0}{d\\_p \\sqrt{\\pi}} \\Theta^{3/2}\n$$\n这里有关于$\\Theta$的非线性项，程序里实际上是对$\\Theta$作了部分隐式处理，从 `gammaCoeff`的定义可以看出来：\n$$\n\\gamma\\_{Coeff} = \\frac{12(1-e^2)\\varepsilon\\_s^2\\rho g\\_0}{d\\_p } \\sqrt{\\frac{\\Theta}{\\pi}}\n$$\n\n颗粒温度方程中的$J_s$ 也分成了两项来处理，$J_s = J_1 - J_2$\n$J_1$的处理很简单，在公式里，$J_1 = 3\\beta \\Theta$，而在代码当中，变量 `J1` 定义为 `3*beta`，而 `Theta` 则作隐式处理。   \n\n$J_2$也人为作了隐式处理，文献中$J_2$的表达式为\n$$\nJ_2 = \\frac{\\beta^2 d_p |U_g-U_s|^2}{4\\varepsilon_s\\rho\\sqrt{\\pi \\Theta}}\n$$\n\n程序里则将$J_2$除以$\\Theta$以后作为系数，以实现对$\\Theta$进行隐式处理。\n注意这里要说明一下\"Sp\"和\"SuSp\"的区别，这个在\"Programmer's Guide\"里有说明，此外这个[网页](https://openfoamwiki.net/index.php/HowTo_Adding_a_new_transport_equation)里也有说明。可是，为什么固相压力项要用\"SuSp\"，这个也暂时不明白。\n\n计算颗粒温度的代数方程的具体公式这里不写了，可以参考文献。\n\n最后，上面提到的由于颗粒摩擦作用产生的颗粒压力和颗粒粘度，只在颗粒体积分率很大的区域才需要启用，算例中需要设定一个值，对于颗粒相体积分率大于一个设定值时， `alphaMinFriction`，只有颗粒相体积分率超过这个值时，才会启用由于颗粒相摩擦而产生的压力和粘度。\n\n\n","source":"_posts/kineticTheoryModel.md","raw":"title: \"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之 kineticTheoryModel\"\ndate: 2015-09-19 10:26:09\ncomments: true\ntags:\n - OpenFOAM\n - Code Explained\ncategories:\n - OpenFOAM\n---\n\nOpenFOAM 中双流体模型的 kineticTheoryModel 是以 \" *Derivation, implementation, and validation of computer simulation models for gas-solid fluidized beds*, B.G.M. van Wachem, Ph.D. Thesis, Delft University of Technology, Amsterdam, 2000. \" 为蓝本来设计的，下面分析这个类的代码。需要注意的是，这个类需要调用一些别的类（如 viscosityModel 等，后面会一一分析）来完成其功能。\n\n<!--more-->\n\n\n\n\n\n#### 1. 头文件 kineticTheoryModel.H  \n\n头文件中要注意的是 kineticTheoryModel 类的继承关系，以及六个子类的智能指针作为 kineticTheoryModel 类的数据成员。\n\n```\nclass kineticTheoryModel\n:\n    public eddyViscosity\n    <\n        RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >\n    > // 继承自湍流类，所以这里 kineticTheoryModel 跟湍流类使用同样的接口\n{\n    // Private data\n\n        // Input Fields\n            const phaseModel& phase_;\n            \n        // Sub-models \n\t// 下面五个 sub models 是kineticTheoryModel类为了实现其功能需要调用的类。\n\t//子类的智能指针定义为当前类的数据成员。注意这里的 \"kineticTheoryModels\" 是 namespace。\n        \n            //- Run-time selected viscosity model\n            autoPtr<kineticTheoryModels::viscosityModel> viscosityModel_;\n            \n            //- Run-time selected conductivity model\n            autoPtr<kineticTheoryModels::conductivityModel> conductivityModel_;\n            //- Run-time selected radial distribution model\n            autoPtr<kineticTheoryModels::radialModel> radialModel_;\n\n            //- Run-time selected granular pressure model\n            autoPtr<kineticTheoryModels::granularPressureModel>\n                granularPressureModel_;\n\n            //- Run-time selected frictional stress model\n            autoPtr<kineticTheoryModels::frictionalStressModel>\n                frictionalStressModel_;\n\n  ......\n  ......\n\n```\n\n#### 2. 构造函数\n\n注意这里向基类传递的参数，这里的湍流类的继承关系比较复杂，比单相湍流复杂很多，后面会有具体的分析\n```\nFoam::RASModels::kineticTheoryModel::kineticTheoryModel\n(\n    const volScalarField& alpha,\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& phase,\n    const word& propertiesName,\n    const word& type\n)\n:\n    eddyViscosity<RASModel<PhaseCompressibleTurbulenceModel<phaseModel> > >\n    (\n        type,\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        phase,\n        propertiesName\n    ),\n\n    phase_(phase),\n\n    viscosityModel_\n    (\n        kineticTheoryModels::viscosityModel::New\n        (\n            this->coeffDict_ // coeffDict_ 是 RASModel的成员\n        )\n    ),\n    conductivityModel_\n    (\n        kineticTheoryModels::conductivityModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n    radialModel_\n    (\n        kineticTheoryModels::radialModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n    granularPressureModel_\n    (\n        kineticTheoryModels::granularPressureModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n    frictionalStressModel_\n    (\n        kineticTheoryModels::frictionalStressModel::New\n        (\n            this->coeffDict_\n        )\n    ),\n\n......\n......\n```\n\n#### 3. 主要成员函数\n```\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::k() const\n{\n    // notImplemented 是 Error.H中定义的一个函数，用于提示某个模型或函数没有实现。\n    notImplemented(\"kineticTheoryModel::k()\"); \n    return nut_;\n}\n\n\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::epsilon() const\n{\n    notImplemented(\"kineticTheoryModel::epsilon()\");\n    return nut_;\n}\n\n```\n `k` 和  `epsilon` 是两个从基类继承下来的函数，这两个函数在基类中是纯虚函数，所以虽然 kineticTheoryModel 用不到它们，但是还是需要进行定义，否则 kineticTheoryModel 类就将是一个虚基类，从而无法创建对象了。\n\n\n```\nFoam::tmp<Foam::volSymmTensorField>\nFoam::RASModels::kineticTheoryModel::R() const\n{\n    return tmp<volSymmTensorField>\n    (\n        new volSymmTensorField\n        (\n            IOobject\n            (\n                IOobject::groupName(\"R\", this->U_.group()),\n                this->runTime_.timeName(),\n                this->mesh_,\n                IOobject::NO_READ,\n                IOobject::NO_WRITE\n            ),\n          - (this->nut_)*dev(twoSymm(fvc::grad(this->U_)))\n          - (lambda_*fvc::div(this->phi_))*symmTensor::I\n        )\n    );\n}\n\n```\nR 函数返回所谓的雷诺应力，其实跟单相湍流类的 R 函数返回值有点差别：\n$$\n-\\nu_t[\\nabla U + \\nabla U^T-\\frac{2}{3}(\\nabla \\cdot U)\\cdot \\mathrm{I}] - \\lambda(\\nabla \\cdot U)\\cdot \\mathrm{I}\n$$\n这里的 R 与下面的 devRoReff 函数返回值只相差一个 rho。\n而单相湍流模型的 R 则为：\n$$\n\\frac{2}{3}k\\cdot \\mathrm{I}- \\nu_t (\\nabla U + \\nabla U^T)\n$$\n\n```\n\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::pPrime() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n\n\nFoam::tmp<Foam::surfaceScalarField>\nFoam::RASModels::kineticTheoryModel::pPrimef() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return fvc::interpolate\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n```\npPrime 和 pPrimef 两个函数，返回的是固相压力对固相孔隙率的导数（$\\partial P_s/\\partial \\varepsilon_s$）。\n\n\n\n两个在\"UEqn.H\" 被动量方程调用的函数\n```\nFoam::tmp<Foam::volSymmTensorField>\nFoam::RASModels::kineticTheoryModel::devRhoReff() const\n{\n    return tmp<volSymmTensorField>\n    (\n        new volSymmTensorField\n        (\n            IOobject\n            (\n                IOobject::groupName(\"devRhoReff\", this->U_.group()),\n                this->runTime_.timeName(),\n                this->mesh_,\n                IOobject::NO_READ,\n                IOobject::NO_WRITE\n            ),\n          - (this->rho_*this->nut_)\n           *dev(twoSymm(fvc::grad(this->U_)))\n          - ((this->rho_*lambda_)*fvc::div(this->phi_))*symmTensor::I\n        )\n    );\n}\n\n\nFoam::tmp<Foam::fvVectorMatrix>\nFoam::RASModels::kineticTheoryModel::divDevRhoReff\n(\n    volVectorField& U\n) const\n{\n    return\n    (\n      - fvm::laplacian(this->rho_*this->nut_, U)\n      - fvc::div\n        (\n            (this->rho_*this->nut_)*dev2(T(fvc::grad(U)))\n          + ((this->rho_*lambda_)*fvc::div(this->phi_))\n           *dimensioned<symmTensor>(\"I\", dimless, symmTensor::I)\n        )\n    );\n}\n```\n对应的公式分别为：\n**devRhoReff**\n$$\n\\- \\rho  \\nu_t \\cdot dev(\\nabla U + \\nabla U^T) - \\rho \\lambda(\\nabla \\cdot U)\\cdot  \\mathrm{I} \\\\\\\\\n = - \\rho  \\nu_t(\\nabla U + \\nabla U^T) +  \\rho  \\nu_t \\frac{2}{3}(\\nabla \\cdot U)\\cdot \\mathrm{I} - \\rho \\lambda (\\nabla \\cdot U)\\cdot  \\mathrm{I}\n$$\n\n**divDevRhoReff**\n$$\n\\ - \\nabla \\cdot (\\rho \\nu_t \\nabla U) - \\nabla \\cdot \\left [\\rho \\nu_t \\nabla U^T\\ - \\rho \\nu_t \\frac{2}{3}(\\nabla \\cdot U)\\cdot \\mathrm{I} \\right ] + \\rho \\lambda (\\nabla \\cdot U) \\cdot \\mathrm{I} )\n$$\n\n\n `correct`是计算颗粒温度 `Theta` 的函数，这是 kineticTheoryModel 类最重要的部分。\n```\nvoid Foam::RASModels::kineticTheoryModel::correct()\n{\n    // Local references\n    volScalarField alpha(max(this->alpha_, scalar(0)));\n    const volScalarField& rho = phase_.rho();\n    const surfaceScalarField& alphaRhoPhi = this->alphaRhoPhi_;\n    const volVectorField& U = this->U_; // 当前相的速度\n    const volVectorField& Uc_ = phase_.fluid().otherPhase(phase_).U(); // 另一相的速度\n\n    const scalar sqrtPi = sqrt(constant::mathematical::pi);\n    dimensionedScalar ThetaSmall(\"ThetaSmall\", Theta_.dimensions(), 1.0e-6);\n    dimensionedScalar ThetaSmallSqrt(sqrt(ThetaSmall));\n\n    tmp<volScalarField> tda(phase_.d()); // 颗粒直径\n    const volScalarField& da = tda();\n\n    tmp<volTensorField> tgradU(fvc::grad(this->U_));\n    const volTensorField& gradU(tgradU());\n    volSymmTensorField D(symm(gradU));\n\n    // Calculating the radial distribution function \n    // 调用 radialModel 类 来计算径向分布函数\n    gs0_ = radialModel_->g0(alpha, alphaMinFriction_, alphaMax_);\n\n    if (!equilibrium_) // 如果 equilibrium_ = off ，那么将用这个偏微分方程来计算颗粒温度，否则将改用一个代数方程，见下文\n    {\n        // Particle viscosity (Table 3.2, p.47)\n\t// 调用viscosityModel 来更新颗粒相的粘度\n        nut_ = viscosityModel_->nu(alpha, Theta_, gs0_, rho, da, e_);\n\n        volScalarField ThetaSqrt(sqrt(Theta_));\n\n        // Bulk viscosity  p. 45 (Lun et al. 1984). \n        lambda_ = (4.0/3.0)*sqr(alpha)*da*gs0_*(1.0 + e_)*ThetaSqrt/sqrtPi;\n\n        // Stress tensor, Definitions, Table 3.1, p. 43\n        volSymmTensorField tau // 颗粒应力\n        (\n            rho*(2.0*nut_*D + (lambda_ - (2.0/3.0)*nut_)*tr(D)*I)\n        );\n\n        // Dissipation (Eq. 3.24, p.50)\n        volScalarField gammaCoeff //颗粒动能耗散项\n        (\n            12.0*(1.0 - sqr(e_))\n           *max(sqr(alpha), residualAlpha_)\n           *rho*gs0_*(1.0/da)*ThetaSqrt/sqrtPi\n        );\n\n        // Drag // 调用曳力模型计算曳力系数\n        volScalarField beta(phase_.fluid().drag(phase_).K());\n\n        // Eq. 3.25, p. 50 Js = J1 - J2\n        volScalarField J1(3.0*beta);\n        volScalarField J2\n        (\n            0.25*sqr(beta)*da*magSqr(U - Uc_)\n           /(\n               max(alpha, residualAlpha_)*rho\n              *sqrtPi*(ThetaSqrt + ThetaSmallSqrt)\n            )\n        );\n\n        // particle pressure - coefficient in front of Theta (Eq. 3.22, p. 45)\n        volScalarField PsCoeff\n        (\n            granularPressureModel_->granularPressureCoeff\n            (\n                alpha,\n                gs0_,\n                rho,\n                e_\n            )\n        );\n\n        // 'thermal' conductivity (Table 3.3, p. 49)\n\t// 调用 conductivityModel 计算颗粒脉动能量的传导系数\n        kappa_ = conductivityModel_->kappa(alpha, Theta_, gs0_, rho, da, e_);\n\n        // Construct the granular temperature equation (Eq. 3.20, p. 44)\n        // NB. note that there are two typos in Eq. 3.20:\n        //     Ps should be without grad\n        //     the laplacian has the wrong sign\n\t// 构建颗粒温度方程，注意，开头提到的文献里的颗粒温度方程有两处 typo，\n\t//下面的代码修复了这两处错误，后文会给出正确的公式。\n        fvScalarMatrix ThetaEqn\n        (\n            1.5*\n            (\n                fvm::ddt(alpha, rho, Theta_)\n              + fvm::div(alphaRhoPhi, Theta_)\n              - fvc::Sp(fvc::ddt(alpha, rho) + fvc::div(alphaRhoPhi), Theta_)\n            )\n          - fvm::laplacian(kappa_, Theta_, \"laplacian(kappa,Theta)\")\n         ==\n            fvm::SuSp(-((PsCoeff*I) && gradU), Theta_)\n          + (tau && gradU)\n          + fvm::Sp(-gammaCoeff, Theta_)\n          + fvm::Sp(-J1, Theta_)\n          + fvm::Sp(J2/(Theta_ + ThetaSmall), Theta_)\n        );\n\n        ThetaEqn.relax();\n        ThetaEqn.solve();\n    }\n    else // 如果 equilibrium = on， 将使用一个代数方程来计算颗粒温度。\n    {\n        // Equilibrium => dissipation == production\n        // Eq. 4.14, p.82\n        volScalarField K1(2.0*(1.0 + e_)*rho*gs0_);\n        volScalarField K3\n        (\n            0.5*da*rho*\n            (\n                (sqrtPi/(3.0*(3.0 - e_)))\n               *(1.0 + 0.4*(1.0 + e_)*(3.0*e_ - 1.0)*alpha*gs0_)\n               +1.6*alpha*gs0_*(1.0 + e_)/sqrtPi\n            )\n        );\n\n        volScalarField K2\n        (\n            4.0*da*rho*(1.0 + e_)*alpha*gs0_/(3.0*sqrtPi) - 2.0*K3/3.0\n        );\n\n        volScalarField K4(12.0*(1.0 - sqr(e_))*rho*gs0_/(da*sqrtPi));\n\n        volScalarField trD\n        (\n            alpha/(alpha + residualAlpha_)\n           *fvc::div(this->phi_)\n        );\n        volScalarField tr2D(sqr(trD));\n        volScalarField trD2(tr(D & D));\n\n        volScalarField t1(K1*alpha + rho);\n        volScalarField l1(-t1*trD);\n        volScalarField l2(sqr(t1)*tr2D);\n        volScalarField l3\n        (\n            4.0\n           *K4\n           *alpha\n           *(2.0*K3*trD2 + K2*tr2D)\n        );\n\n        Theta_ = sqr\n        (\n            (l1 + sqrt(l2 + l3))\n           /(2.0*max(alpha, residualAlpha_)*K4)\n        );\n\n        kappa_ = conductivityModel_->kappa(alpha, Theta_, gs0_, rho, da, e_);\n    }\n\n\n   // 限定 颗粒温度的上下限。\n  // max 和 min 函数的定义，没有找到。经验证， max 的作用是让小于0的归零， min 是让大于100的等于100。\n    Theta_.max(0);\n    Theta_.min(100);\n\n    {\n\t//利用先得到的颗粒温度更新颗粒相的粘度\n        // particle viscosity (Table 3.2, p.47)\n        nut_ = viscosityModel_->nu(alpha, Theta_, gs0_, rho, da, e_);\n\n        volScalarField ThetaSqrt(sqrt(Theta_));\n\n        // Bulk viscosity  p. 45 (Lun et al. 1984).\n        lambda_ = (4.0/3.0)*sqr(alpha)*da*gs0_*(1.0 + e_)*ThetaSqrt/sqrtPi;\n\n        // Frictional pressure // 计算由于颗粒之间的摩擦作用产生的一个等效的颗粒相压力作用。\n        volScalarField pf\n        (\n            frictionalStressModel_->frictionalPressure\n            (\n                alpha,\n                alphaMinFriction_,\n                alphaMax_\n            )\n        );\n\n        // Add frictional shear viscosity, Eq. 3.30, p. 52\n       // 将颗粒摩擦产生的颗粒相粘度加到由颗粒温度计算得到的颗粒相粘度中，作为总的颗粒相粘度\n        nut_ += frictionalStressModel_->nu \n        (\n            alpha,\n            alphaMax_,\n            pf/rho,\n            D\n        );\n\n        // Limit viscosity\n\t// 限定颗粒相粘度的上限\n        nut_.min(100);\n    }\n\n    if (debug)\n    {\n        Info<< typeName << ':' << nl\n            << \"    max(Theta) = \" << max(Theta_).value() << nl\n            << \"    max(nut) = \" << max(nut_).value() << endl;\n    }\n}\n```\n\n这里重点关注一下 `ThetaEqn` 的写法。\n```\nfvScalarMatrix ThetaEqn\n  (\n      1.5*\n      (\n          fvm::ddt(alpha, rho, Theta_)\n        + fvm::div(alphaRhoPhi, Theta_)\n        - fvc::Sp(fvc::ddt(alpha, rho) + fvc::div(alphaRhoPhi), Theta_)\n      )\n    - fvm::laplacian(kappa_, Theta_, \"laplacian(kappa,Theta)\")\n   ==\n      fvm::SuSp(-((PsCoeff*I) && gradU), Theta_)\n    + (tau && gradU)\n    + fvm::Sp(-gammaCoeff, Theta_)\n    + fvm::Sp(-J1, Theta_)\n    + fvm::Sp(J2/(Theta_ + ThetaSmall), Theta_)\n );\n```\n对应的偏微分方程是，\n$$\n\\frac{3}{2}\\left [ \\frac{\\partial }{\\partial t}(\\varepsilon_s \\rho_s \\Theta)+ \\nabla \\cdot (\\varepsilon_s \\rho_s \\Theta U_s) \\right ] = (-P_s \\mathrm{I} + \\tau_s):\\nabla U_s + \\nabla \\cdot (\\kappa_s \\nabla \\Theta) - \\gamma_s - J_s\n$$\n下面将公式与代码一一对应。代码里跟公式相比，多了一项\n$$\n-\\Theta\\frac{\\partial }{\\partial t}(\\varepsilon_s \\rho_s) - \\Theta \\nabla \\cdot (\\varepsilon_s \\rho_s U_s)\n$$\n这样，相当于代码对应的前两项是\n$$\n\\varepsilon_s \\rho_s \\frac{\\partial \\Theta}{\\partial t} + \\varepsilon_s \\rho_s U_s \\cdot \\nabla \\Theta\n$$\n这样做的目的仍不是很明确。\n\nLaplacian 项不需多言，剩下的几项，全都当作了源项来处理。\n$(-P\\_s \\mathrm{I} + \\tau\\_s)\\:\\nabla U\\_s$ 拆开成了两项，分别对应 `fvm::SuSp(-((PsCoeff*I) && gradU), Theta_)` 和 `(tau && gradU)` 。第一项，由于$P_s$是$\\Theta$ 的函数，所以，在固相压力类中，返回的值是固相压力系数（固相压力除以颗粒温度），在这里将$\\Theta$进行了隐式处理。而由于$\\tau$与$\\nabla U_s$与$\\Theta$无关，所以就只当成一般的源项了。\n\n$\\gamma$项的处理与颗粒压力类似，也是只定义 `gammaCoeff` ，然后将$\\Theta$作隐式处理。由于实际上$\\gamma$的表达式\n$$\n\\gamma = \\frac{12(1-e^2)\\varepsilon\\_s^2\\rho g\\_0}{d\\_p \\sqrt{\\pi}} \\Theta^{3/2}\n$$\n这里有关于$\\Theta$的非线性项，程序里实际上是对$\\Theta$作了部分隐式处理，从 `gammaCoeff`的定义可以看出来：\n$$\n\\gamma\\_{Coeff} = \\frac{12(1-e^2)\\varepsilon\\_s^2\\rho g\\_0}{d\\_p } \\sqrt{\\frac{\\Theta}{\\pi}}\n$$\n\n颗粒温度方程中的$J_s$ 也分成了两项来处理，$J_s = J_1 - J_2$\n$J_1$的处理很简单，在公式里，$J_1 = 3\\beta \\Theta$，而在代码当中，变量 `J1` 定义为 `3*beta`，而 `Theta` 则作隐式处理。   \n\n$J_2$也人为作了隐式处理，文献中$J_2$的表达式为\n$$\nJ_2 = \\frac{\\beta^2 d_p |U_g-U_s|^2}{4\\varepsilon_s\\rho\\sqrt{\\pi \\Theta}}\n$$\n\n程序里则将$J_2$除以$\\Theta$以后作为系数，以实现对$\\Theta$进行隐式处理。\n注意这里要说明一下\"Sp\"和\"SuSp\"的区别，这个在\"Programmer's Guide\"里有说明，此外这个[网页](https://openfoamwiki.net/index.php/HowTo_Adding_a_new_transport_equation)里也有说明。可是，为什么固相压力项要用\"SuSp\"，这个也暂时不明白。\n\n计算颗粒温度的代数方程的具体公式这里不写了，可以参考文献。\n\n最后，上面提到的由于颗粒摩擦作用产生的颗粒压力和颗粒粘度，只在颗粒体积分率很大的区域才需要启用，算例中需要设定一个值，对于颗粒相体积分率大于一个设定值时， `alphaMinFriction`，只有颗粒相体积分率超过这个值时，才会启用由于颗粒相摩擦而产生的压力和粘度。\n\n\n","slug":"kineticTheoryModel","published":1,"updated":"2015-09-25T09:14:38.738Z","layout":"post","photos":[],"link":"","_id":"cioiqegcv0029z8mb81a3mw5p"},{"title":"Hello World","date":"2015-04-04T02:00:00.000Z","updated":"2015-04-10T14:57:00.000Z","comments":1,"_content":"Welcome to [Hexo](http://hexo.io/)! This is your very first post. Check [documentation](http://hexo.io/docs/) for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](http://hexo.io/docs/troubleshooting.html) or you can ask me on [GitHub](https://github.com/hexojs/hexo/issues).\n\n## Quick Start\n\n### Create a new post\n\n``` bash\n$ hexo new \"My New Post\"\n```\n\nMore info: [Writing](http://hexo.io/docs/writing.html)\n\n### Run server\n\n``` bash\n$ hexo server\n```\n\nMore info: [Server](http://hexo.io/docs/server.html)\n\n### Generate static files\n\n``` bash\n$ hexo generate\n```\n\nMore info: [Generating](http://hexo.io/docs/generating.html)\n\n### Deploy to remote sites\n\n``` bash\n$ hexo deploy\n```\n\nMore info: [Deployment](http://hexo.io/docs/deployment.html)\n\n#### Test code\n\n```C++\n#include<iostream>\nint main(int argc, char* argv[])\n{\n    std::cout<<\"hello world!\" << std::endl;\n    return 0;\n}\n```\n#### 中文支持测试\n看看是否支持中文。\n\n####数学公式\n$$ x=\\frac{-b\\pm \\sqrt{b^2-4ac}}{2a} $$\n\n#### 本地图片测试\n![Jupiter](/image/juperter_impact.jpg)\n\n#### gif图片测试\n![cfb](/image/cfb.gif)\n","source":"_posts/hello-world.md","raw":"title: Hello World\ndate: 2015/04/04 10:00:00\nupdated: 2015/04/10 22:57:00\ncomments: true\ntags: \n - test\ncategories: \n - test\n---\nWelcome to [Hexo](http://hexo.io/)! This is your very first post. Check [documentation](http://hexo.io/docs/) for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](http://hexo.io/docs/troubleshooting.html) or you can ask me on [GitHub](https://github.com/hexojs/hexo/issues).\n\n## Quick Start\n\n### Create a new post\n\n``` bash\n$ hexo new \"My New Post\"\n```\n\nMore info: [Writing](http://hexo.io/docs/writing.html)\n\n### Run server\n\n``` bash\n$ hexo server\n```\n\nMore info: [Server](http://hexo.io/docs/server.html)\n\n### Generate static files\n\n``` bash\n$ hexo generate\n```\n\nMore info: [Generating](http://hexo.io/docs/generating.html)\n\n### Deploy to remote sites\n\n``` bash\n$ hexo deploy\n```\n\nMore info: [Deployment](http://hexo.io/docs/deployment.html)\n\n#### Test code\n\n```C++\n#include<iostream>\nint main(int argc, char* argv[])\n{\n    std::cout<<\"hello world!\" << std::endl;\n    return 0;\n}\n```\n#### 中文支持测试\n看看是否支持中文。\n\n####数学公式\n$$ x=\\frac{-b\\pm \\sqrt{b^2-4ac}}{2a} $$\n\n#### 本地图片测试\n![Jupiter](/image/juperter_impact.jpg)\n\n#### gif图片测试\n![cfb](/image/cfb.gif)\n","slug":"hello-world","published":1,"layout":"post","photos":[],"link":"","_id":"cioiqegd4002dz8mbga0xvygg"},{"title":"fvOptions 之 semiImplicitSource","date":"2016-03-20T07:24:41.000Z","comments":1,"_content":"\n上篇浅析了 fvOptions 框架的结构，这篇来看一个具体的源项类： `semiImplicitSource` 。\n\n<!--more-->\n\n先来看看这个源项代码中的关键部分。\n\n+ SemiImplicitSource.C\n```cpp\n#include \"SemiImplicitSource.H\"\n#include \"fvMesh.H\"\n#include \"fvMatrices.H\"\n#include \"DimensionedField.H\"\n#include \"fvmSup.H\"\n\ntemplate<class Type>\nconst Foam::wordList Foam::fv::SemiImplicitSource<Type>::volumeModeTypeNames_\n(\n    IStringStream(\"(absolute specific)\")()  // 初始化 volumeModeTypeNames_，这里有两种模式， `absolute` 和 `specific` ，具体含义下面会解释。 \n);\n\ntemplate<class Type>\ntypename Foam::fv::SemiImplicitSource<Type>::volumeModeType\nFoam::fv::SemiImplicitSource<Type>::wordToVolumeModeType // 将字符串转换成 volumeModeType\n(\n    const word& vmtName\n) const\n{\n    forAll(volumeModeTypeNames_, i)\n    {\n        if (vmtName == volumeModeTypeNames_[i])\n        {\n            return volumeModeType(i);\n        }\n    }\n\n    FatalErrorIn\n    (\n        \"SemiImplicitSource<Type>::volumeModeType\"\n        \"SemiImplicitSource<Type>::wordToVolumeModeType(const word&)\"\n    )   << \"Unknown volumeMode type \" << vmtName\n        << \". Valid volumeMode types are:\" << nl << volumeModeTypeNames_\n        << exit(FatalError);\n\n    return volumeModeType(0);\n}\n\ntemplate<class Type>\nFoam::word Foam::fv::SemiImplicitSource<Type>::volumeModeTypeToWord\n(\n    const volumeModeType& vmtType\n) const\n{\n    if (vmtType > volumeModeTypeNames_.size())\n    {\n        return \"UNKNOWN\";\n    }\n    else\n    {\n        return volumeModeTypeNames_[vmtType];\n    }\n}\n\ntemplate<class Type>\nvoid Foam::fv::SemiImplicitSource<Type>::setFieldData(const dictionary& dict)\n{\n    fieldNames_.setSize(dict.toc().size());\n    injectionRate_.setSize(fieldNames_.size());\n\n    applied_.setSize(fieldNames_.size(), false);\n\n    label i = 0;\n    forAllConstIter(dictionary, dict, iter)\n    {\n        fieldNames_[i] = iter().keyword();\n        dict.lookup(iter().keyword()) >> injectionRate_[i];\n        i++;\n    }\n\n    // Set volume normalisation\n    if (volumeMode_ == vmAbsolute)\n    {\n        VDash_ = V_;\n    }\n}\n\n\n// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //\n\ntemplate<class Type>\nFoam::fv::SemiImplicitSource<Type>::SemiImplicitSource\n(\n    const word& name,\n    const word& modelType,\n    const dictionary& dict,\n    const fvMesh& mesh\n)\n:\n    option(name, modelType, dict, mesh),\n    volumeMode_(vmAbsolute), volumeMode 初始值为 vmAbsolute，也就是字典里的 absolute\n    VDash_(1.0), // VDash 初始值为 1.0\n    injectionRate_()\n{\n    read(dict);\n}\n\n\ntemplate<class Type>\nvoid Foam::fv::SemiImplicitSource<Type>::addSup // 这个是最关键的函数，求解器里的 fvOptions(T)，最终就是转换为调用这个函数。\n(\n    fvMatrix<Type>& eqn,\n    const label fieldI\n)\n{\n    if (debug)\n    {\n        Info<< \"SemiImplicitSource<\" << pTraits<Type>::typeName\n            << \">::addSup for source \" << name_ << endl;\n    }\n\n    // psi 表示方程中的未知量，比如，eqn(fvm::ddt(T))，则psi其实就相当于T，其量纲也与T的量纲一致。\n    const GeometricField<Type, fvPatchField, volMesh>& psi = eqn.psi();\n\n// 创建一个场 Su，其量纲为方程eqn的量纲除以体积的量纲。注意，经测试，假设eqn为（fvm::ddt(T)），则eqn的量纲为k.m^3/s。\n    DimensionedField<Type, volMesh> Su\n    (\n        IOobject\n        (\n            name_ + fieldNames_[fieldI] + \"Su\",\n            mesh_.time().timeName(),\n            mesh_,\n            IOobject::NO_READ,\n            IOobject::NO_WRITE\n        ),\n        mesh_,\n        dimensioned<Type>\n        (\n            \"zero\",\n            eqn.dimensions()/dimVolume,\n            pTraits<Type>::zero\n        ),\n        false\n    );\n\n    // 这一句的意思是，将属于cells_这个集合的网格的Su赋值为fvoptions里所设置的第一个参数的值除以体积VDash。这里的VDash,如果模式为absolute，则值为cells_这个集合的网格体积之和，如果模式为specific，则其值为1. \n    // UUIndirectList<Type>(Su, cells_)这一句是利用Su和cells为参数，构建一个UUIndirectList类的临时对象，并调用这个类的重载的“=”操作符对Su进行重新赋值。\n    \n    UIndirectList<Type>(Su, cells_) = injectionRate_[fieldI].first()/VDash_;\n\n    DimensionedField<scalar, volMesh> Sp\n    (\n        IOobject\n        (\n            name_ + fieldNames_[fieldI] + \"Sp\",\n            mesh_.time().timeName(),\n            mesh_,\n            IOobject::NO_READ,\n            IOobject::NO_WRITE\n        ),\n        mesh_,\n        dimensioned<scalar>\n        (\n            \"zero\",\n            Su.dimensions()/psi.dimensions(),\n            0.0\n        ),\n        false\n    );\n    \n    UIndirectList<scalar>(Sp, cells_) = injectionRate_[fieldI].second()/VDash_;\n\n    // fvMatrix<Type> 类中对“+=”操作符进行了重载，所以，eqn与Su的相加，相当于eqn+Su*mesh.V()，要不然eqn与Su的量纲不一致。\n    eqn += Su + fvm::SuSp(Sp, psi);\n}\n\n```\n下面用一个例子来说明 `semiImplicitSource` 的作用。前提到 `scalarTransportFoam` 是使用 fvOptions 的一个最简单的求解器，这里对该求解器进一步简化，只保留瞬变项，对流和扩散项都删去，来验证 `semiImplicitSource` 的作用。 \n修改之后的 `TEqn` 为\n```\ntmp TEqn\n(\n    fvm::ddt(T)  == fvOptions(T)\n);\n```\n然后，`fvOptions` 词典文件的设置如下：\n```\n        firstHeatSource\n        {\n            type scalarSemiImplicitSource;\n            active          true;\n            selectionMode   cellZone;\n            cellZone        boxSourceZone;\n            scalarSemiImplicitSourceCoeffs \n            {\n                volumeMode absolute;\n                injectionRateSuSp \n                {\n                    T (0.05 0);\n                }\n            }\n        }\n```\n上述设置，相当于求解如下方程\n$$\n\\frac{\\partial T}{\\partial t}=S\\_u + S\\_p\\cdot T\n$$\n其中 $S\\_u=0.05, S\\_p = 0$ 。\n反观上面对代码的分析，可知对于当前的设置，$S\\_u$ 的量纲为 `TEqn` 的量纲除以体积量纲，$S\\_p$ 的量为 $S\\_u$ 量纲除以 `T` 的量纲，这与上面给出的方程是一致的。\n但是，要注意， `SemiImplicitSource` 有两个模式：absolute 和 specific，区别在于代码里的 `VDash` 的取值不一样。对于 absolute 模式，`VDash = V`，即所选的 cellZone 的体积；对于 specific 模式，`VDash = 1.0`。而代码里的 `Su` 和 `Sp` 的值都是用在`fvOptions` 词典文件设置的值除以 `VDash` 。\n所以，确切地说，求解的应该是如下积分方程：\n$$\n\\int\\_V \\frac{\\partial T}{\\partial t} dV - \\int\\_V \\nabla \\cdot (D\\_T \\nabla T) dV = \\int\\_V (\\frac{S\\_u}{V\\_{Dash}}+\\frac{S\\_p}{V\\_{Dash}}T )dV\n$$\n其中 $V\\_{Dash}$ 是选定的 cellZone 的体积。\n为了验证以上的推演，作了如下两个测试：\n+ 源项参数设置为`T (0.01 0)`，源项作用的区域为一个体积为$V\\_{Dash}=0.001$ 的 cellZone，为了消除热量传递，设置 $D\\_T = 0$，初始整个区域的 T 均为0， 模拟时间为1 s。\n按照上述的推演，如果是 `absolute` 模式，最终 1 s 时选定的 cellZone 的温度将是 $T=t\\cdot \\frac{S\\_u}{V\\_{Dash}}=1s\\cdot\\frac{0.01 k/s}{0.001}=10k$；如果是 `specific` 模式，那么最终1 s 时选定的 cellZone 的温度将是 $T=1s\\cdot\\frac{0.01 k/s}{1.0}=0.01k$。以上结果在测试算例中得到了证实。\n+ 源项参数设置为`T (1.0 2.0)`，同样设置 $D\\_T = 0$。这种情况下，可以先求微分方程 \n$$\\frac{\\partial T}{\\partial t}=x+yT$$\n的解，经简单计算得到 \n$$T=\\frac{e^{yt}\\cdot e^{yc}-x}{y}$$\n$c$ 为任意常数。\n若 $T|\\,_{t=0}=0k$，则可以得到定解为\n$$T=\\frac{e^{yt}\\cdot x-x}{y}$$\n若使用`specific` 模式，则根据当前的设置得到1 s时的解为\n$$T=\\frac{e^{2\\cdot 1}\\cdot 1 -1}{2}=3.194528 k$$\n算例测试结果为：\n+ 时间离散格式:Euler，$\\Delta T=0.001s$，$T=3.20193k$；\n+ 时间离散格式：Euler，$\\Delta T=0.0001s$，$T=3.19527k$，可见减小时间步后结果与解析解吻合度提高了很多。\n+ 时间离散格式：CrankNicolson，$\\Delta T=0.001s$，$T=3.19454k$。可见用高阶的时间离散格式，在同样时间步下能得到误差更小的结果。\n\n同样，如果用`absolute` 模式，且源项设置为 `T (0.001 0.002)` ，应该能得到一样的结果，实际上算例测试正是如此。由此可以认为推演得到了证实。\n\n至此，`SemiImplicitSource` 这个源项的核心部分就算是明了了。可是，现在测试的是非常简单的情形，如果在求解多个方程，且有多个方程里都加入了源项的情况下，该怎么给不同的方程设置不一样的源项呢？要解决这个问题，需要先理解清楚 `fvOptions` 的调用过程。\n\n\n##### fvOptions 源项的调用过程\n下面至下而上地来看看 fvOptions 的调用过程。\n\n`TEqn`里，有一个调用 `fvOptions` 的语句： `fvOptions(T)`，上一篇讲过， `fvOptions` 的定义为 \n```\nfv::IOoptionList fvOptions(mesh);\n```\n这就很明显了：建立一个 `IOoptionList` 类的对象 `fvOptions`。由此可知，求解器里的 `fvOptions` 是一个对象的名字，因此 `fvOptions(T)` 这种用法也只可能是对象调用类中重载的 `()` 运算符了。从前面的分析，可知 `IOoptionList` 本身很简单，仅是作为一个接口来用的，所以 `()` 与算符的重载要去其父类中去找。 `IOoptionList` 的作用是，从 \"constant\"（优先）或者 \"system\" 目录读取 `fvOptions` 文件，并作为 `IOobject` 类的对象传递给父类 `optionList` （从构造函数的成员初始化列表 `optionList(mesh, *this)` ）\n\n---\n\n接下来，就该进入 `optionList` 类了。这个类里，所有可能出现在求解器代码里函数都有了，包括 `correct` ， `constrain` ， `makeRelative` ， `makeAbsolute` ， `relative` 以及 `()` 运算符的重载。但是，注意这里并没有具体的代码实现，而是通过类似\n```cpp\nforAll(*this, i)\n{\n   this->operator[](i).makeAbsolute(phi);\n}\n```\n调用其他地方的函数。 `optionList` 的一个重要使命是，统计 `fvOptions` 文件里定义了多少个源项，并将每一个源项都作为一个储存起来，然后再根据词典的内容创建特定的源项。核心在于 `reset` 函数。为了说明这一点，先从构造函数看起\n```\nreset(optionsDict(dict));\n```\n可见构造函数里调用了 `reset` 函数，并且用 `optionsDict` 函数的返回值作为 `reset` 函数的参数。前文讲到， `IOoptionList` 类将 `fvOptions` 文件的内容以 `IOobject` 的形式传递给父类 `optionList`，所以，这里的参数 `dict` 可以理解为就是`fvOptions` 文件的内容。`optionsDict` 函数的代码如下：\n```\nconst Foam::dictionary& Foam::fv::optionList::optionsDict\n(\n    const dictionary& dict\n) const\n{\n    if (dict.found(\"options\"))\n    {\n        return dict.subDict(\"options\");\n    }\n    else\n    {\n        return dict;\n    }\n}\n```\n可见，这个函数去 `fvOptions` 文件里查找关键字 `options`，如果找到，就将 `options` 关键字对应的 subDict 内容返回，否则直接返回 `fvOptions` 文件的内容。举例说，形如\n```\nfirstHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.001 0);\n        }\n    }\n}\n```\n的，直接返回，因为这就构成了一个 dictionary；而形如\n```\noptions\n{\n    massSource1\n    {\n        type            scalarSemiImplicitSource;\n        $injector1;\n\n        scalarSemiImplicitSourceCoeffs\n        {\n            volumeMode      absolute;\n            injectionRateSuSp\n            {\n                thermo:rho.air     (1e-3 0); // kg/s\n            }\n        }\n    }\n\n    momentumSource1\n    {\n        type            vectorSemiImplicitSource;\n        $injector1;\n\n        vectorSemiImplicitSourceCoeffs\n        {\n            volumeMode      absolute;\n            injectionRateSuSp\n            {\n                U.air           ((0 -1e-2 0) 0); // kg*m/s^2\n            }\n        }\n    }\n\n    energySource1\n    {\n        type            scalarSemiImplicitSource;\n        $injector1;\n\n        scalarSemiImplicitSourceCoeffs\n        {\n            volumeMode      absolute;\n            injectionRateSuSp\n            {\n                e.air      (500 0); // kg*m^2/s^3\n            }\n        }\n    }\n}\n```\n的，则将options下的每一个 subDict 作为 dictionary 返回。注意，这里的 `dictionary` 类可以理解为一个容器，每一个\n```\nxxxxx\n{\n    ......\n}\n```\n都可以作为容器里的一个成员，容器的容量（size）等于总的成员数，每一个成员，其实就对应一个源项。\n于是，我们知道 `optionsDict` 返回了一个有一定数目成员的容器。再来看 `reset` 函数\n```cpp\nvoid Foam::fv::optionList::reset(const dictionary& dict)\n{\n    // Count number of active fvOptions\n    label count = 0;\n    \n    // 遍历 dict 容器，确定其成员的数目，即确定定义了几个源项。\n    forAllConstIter(dictionary, dict, iter) \n    {\n        if (iter().isDict())\n        {\n            count++;\n        }\n    }\n    this->setSize(count); // setSize 是 PtrList 类的成员，顾名思义，PtrList 是一个 List。PtrList<option> 类的成员是 options 类的对象。\n    \n    label i = 0;\n    // 遍历 dict 容器，根据每一个 dict 容器的成员来建立对应的 option 类的对象，这通过调用 option 类的 New 函数来实现。这是使用 RuntimeSelection 机制的类的很常规的做法。\n    forAllConstIter(dictionary, dict, iter)\n    {\n        if (iter().isDict())\n        {\n            const word& name = iter().keyword(); // keyword 返回的是类似 energySource1 这样的，是这个源项的一个名字\n            const dictionary& sourceDict = iter().dict();\n\n            this->set // set 函数，肯定毫无疑问也是从 PtrList 类中继承而来的\n            (\n                i++,\n                option::New(name, sourceDict, mesh_) // 调用 option 类的 New 函数\n            );\n        }\n    }\n}\n```\n注意，最重要的是， `reset` 里实现了对父类 `PtrList<option>` 的初始化。\n理解了这些，再来看 `correct` 以及 `()` 操作符重载中的代码，就好理解了：\n```cpp\n// 本来 *this 应该是 optionList 类的指针，这里先作个隐式转换，转成 PtrList<option> 类的指针。前面reset函数已经对 PtrList<option> 进行了初始化，使其读入了每一个源项。所以，这里的循环就是对每一个源项进行循环，然后调用对应的函数。operator[]肯定也是在PtrList类中定义的，i 指的是 PtrList 类的第 i 个成员，这里 PtrList 的每一个成员都是一个 option 类的对象，所以，makeAbsolute 函数是定义在 option 类中的函数。\nforAll(*this, i)\n{\n   this->operator[](i).makeAbsolute(phi);\n}\n```\n根据上面的理解，一个很自然的推论是，定义在 `fvOptions` 文件中的源项，其作用是叠加的。也就是说，上述的 `fvOptions(T)`，对定义在 `fvOptions` 文件中的每一个源项，都会调用一次。经测试， \n```\n firstHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.001 0);\n        }\n    }\n}\n        \nsecondHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.0 0.002);\n        }\n    }\n}\n```\n与\n```\nsecondHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.0001 0.002);\n        }\n    }\n}\n```\n的作用是一样的，这证实了源项的作用确实是叠加的。\n此外，还要注意一点，那就是 `optionList` 类中重载的 `()` 运算符，其实是在调用 option 类中的 `addSup` 函数，并且其返回值是 fvMatrix 类的对象。\n```\ntemplate<class Type>\nFoam::tmp<Foam::fvMatrix<Type> > Foam::fv::optionList::operator()\n(\n    GeometricField<Type, fvPatchField, volMesh>& fld,\n    const word& fieldName\n)\n{\n    checkApplied();\n\n    const dimensionSet ds = fld.dimensions()/dimTime*dimVolume;\n\n    tmp<fvMatrix<Type> > tmtx(new fvMatrix<Type>(fld, ds));\n    fvMatrix<Type>& mtx = tmtx();\n\n    forAll(*this, i)\n    {\n        option& source = this->operator[](i);\n\n        label fieldI = source.applyToField(fieldName);\n\n        if (fieldI != -1)\n        {\n            source.setApplied(fieldI);\n\n            if (source.isActive())\n            {\n                if (debug)\n                {\n                    Info<< \"Applying source \" << source.name() << \" to field \"\n                        << fieldName << endl;\n                }\n\n                source.addSup(mtx, fieldI);\n            }\n        }\n    }\n    return tmtx;\n}\n```\n---\n\n理解了以上这些，就可以进入 `option` 类了。\noption 类是所有具体的源项类的基类，这个类里处理了所有源项都需要处理的部分，比如确定起始时间，选择源项作用的区域，这些都是在构造函数里完成的，主要是通过调用 `setSelection` 和 `setCellSet` 两个函数。这里需要注意的是数据成员 `cells_`， `V_`  以及 `fieldNames_`，分别定义源项作用区域的网格id （这里源项的作用区域是以 cell 为基础来指定的，即便是 points 模式，实际上源项作用的区域仍然是 points 所在的 cell。）， 选定区域的体积以及需要开启源项作用的场名（有时候，求解器的多个 Eqn 里有fvOptions，但是实际算例中只想针对特定的场开启源项，这可以通过指定 fieldNames_ 来实现）。\n\n注意 fieldNames 在 `option` 类中并没有初始化，需要在具体的源项类中指定。此外，`makeRelative` 等在求解器中实际调用的函数，在 `option` 类中也并没有进行具体的实现（但不是声明为纯虚函数，仅仅是函数体为空的而已，这里的结果不适合用纯虚函数）\n\n以 `SemiImplicitSource` 为例，主要去看 `addSup` 函数，这个函数，关键的一个参数是`fieldI`，这个参数，指的是 `fieldName_` 这个List的 `applyToField` 函数的返回值，用来判断一个 field 是否要启用源项。`fieldName_` 这个List，是在  `SemiImplicitSource` 类的 `setFieldData` 函数中赋值的，通过读取 `SemiImplicitSourceCoeffs` 中的参数来决定。但是这个 `fieldName_` 的确定方法根据不同的类有不同的做法，要具体分析。\n到此，`fvOpptions` 源项的调用途径就打通了，接下来可以继续具体分析特定的源项了。\n","source":"_posts/fvOptions2.md","raw":"title: \"fvOptions 之 semiImplicitSource\"\ndate: 2016-03-20 15:24:41\ncomments: true\ntags:\n- fvOptions\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n上篇浅析了 fvOptions 框架的结构，这篇来看一个具体的源项类： `semiImplicitSource` 。\n\n<!--more-->\n\n先来看看这个源项代码中的关键部分。\n\n+ SemiImplicitSource.C\n```cpp\n#include \"SemiImplicitSource.H\"\n#include \"fvMesh.H\"\n#include \"fvMatrices.H\"\n#include \"DimensionedField.H\"\n#include \"fvmSup.H\"\n\ntemplate<class Type>\nconst Foam::wordList Foam::fv::SemiImplicitSource<Type>::volumeModeTypeNames_\n(\n    IStringStream(\"(absolute specific)\")()  // 初始化 volumeModeTypeNames_，这里有两种模式， `absolute` 和 `specific` ，具体含义下面会解释。 \n);\n\ntemplate<class Type>\ntypename Foam::fv::SemiImplicitSource<Type>::volumeModeType\nFoam::fv::SemiImplicitSource<Type>::wordToVolumeModeType // 将字符串转换成 volumeModeType\n(\n    const word& vmtName\n) const\n{\n    forAll(volumeModeTypeNames_, i)\n    {\n        if (vmtName == volumeModeTypeNames_[i])\n        {\n            return volumeModeType(i);\n        }\n    }\n\n    FatalErrorIn\n    (\n        \"SemiImplicitSource<Type>::volumeModeType\"\n        \"SemiImplicitSource<Type>::wordToVolumeModeType(const word&)\"\n    )   << \"Unknown volumeMode type \" << vmtName\n        << \". Valid volumeMode types are:\" << nl << volumeModeTypeNames_\n        << exit(FatalError);\n\n    return volumeModeType(0);\n}\n\ntemplate<class Type>\nFoam::word Foam::fv::SemiImplicitSource<Type>::volumeModeTypeToWord\n(\n    const volumeModeType& vmtType\n) const\n{\n    if (vmtType > volumeModeTypeNames_.size())\n    {\n        return \"UNKNOWN\";\n    }\n    else\n    {\n        return volumeModeTypeNames_[vmtType];\n    }\n}\n\ntemplate<class Type>\nvoid Foam::fv::SemiImplicitSource<Type>::setFieldData(const dictionary& dict)\n{\n    fieldNames_.setSize(dict.toc().size());\n    injectionRate_.setSize(fieldNames_.size());\n\n    applied_.setSize(fieldNames_.size(), false);\n\n    label i = 0;\n    forAllConstIter(dictionary, dict, iter)\n    {\n        fieldNames_[i] = iter().keyword();\n        dict.lookup(iter().keyword()) >> injectionRate_[i];\n        i++;\n    }\n\n    // Set volume normalisation\n    if (volumeMode_ == vmAbsolute)\n    {\n        VDash_ = V_;\n    }\n}\n\n\n// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //\n\ntemplate<class Type>\nFoam::fv::SemiImplicitSource<Type>::SemiImplicitSource\n(\n    const word& name,\n    const word& modelType,\n    const dictionary& dict,\n    const fvMesh& mesh\n)\n:\n    option(name, modelType, dict, mesh),\n    volumeMode_(vmAbsolute), volumeMode 初始值为 vmAbsolute，也就是字典里的 absolute\n    VDash_(1.0), // VDash 初始值为 1.0\n    injectionRate_()\n{\n    read(dict);\n}\n\n\ntemplate<class Type>\nvoid Foam::fv::SemiImplicitSource<Type>::addSup // 这个是最关键的函数，求解器里的 fvOptions(T)，最终就是转换为调用这个函数。\n(\n    fvMatrix<Type>& eqn,\n    const label fieldI\n)\n{\n    if (debug)\n    {\n        Info<< \"SemiImplicitSource<\" << pTraits<Type>::typeName\n            << \">::addSup for source \" << name_ << endl;\n    }\n\n    // psi 表示方程中的未知量，比如，eqn(fvm::ddt(T))，则psi其实就相当于T，其量纲也与T的量纲一致。\n    const GeometricField<Type, fvPatchField, volMesh>& psi = eqn.psi();\n\n// 创建一个场 Su，其量纲为方程eqn的量纲除以体积的量纲。注意，经测试，假设eqn为（fvm::ddt(T)），则eqn的量纲为k.m^3/s。\n    DimensionedField<Type, volMesh> Su\n    (\n        IOobject\n        (\n            name_ + fieldNames_[fieldI] + \"Su\",\n            mesh_.time().timeName(),\n            mesh_,\n            IOobject::NO_READ,\n            IOobject::NO_WRITE\n        ),\n        mesh_,\n        dimensioned<Type>\n        (\n            \"zero\",\n            eqn.dimensions()/dimVolume,\n            pTraits<Type>::zero\n        ),\n        false\n    );\n\n    // 这一句的意思是，将属于cells_这个集合的网格的Su赋值为fvoptions里所设置的第一个参数的值除以体积VDash。这里的VDash,如果模式为absolute，则值为cells_这个集合的网格体积之和，如果模式为specific，则其值为1. \n    // UUIndirectList<Type>(Su, cells_)这一句是利用Su和cells为参数，构建一个UUIndirectList类的临时对象，并调用这个类的重载的“=”操作符对Su进行重新赋值。\n    \n    UIndirectList<Type>(Su, cells_) = injectionRate_[fieldI].first()/VDash_;\n\n    DimensionedField<scalar, volMesh> Sp\n    (\n        IOobject\n        (\n            name_ + fieldNames_[fieldI] + \"Sp\",\n            mesh_.time().timeName(),\n            mesh_,\n            IOobject::NO_READ,\n            IOobject::NO_WRITE\n        ),\n        mesh_,\n        dimensioned<scalar>\n        (\n            \"zero\",\n            Su.dimensions()/psi.dimensions(),\n            0.0\n        ),\n        false\n    );\n    \n    UIndirectList<scalar>(Sp, cells_) = injectionRate_[fieldI].second()/VDash_;\n\n    // fvMatrix<Type> 类中对“+=”操作符进行了重载，所以，eqn与Su的相加，相当于eqn+Su*mesh.V()，要不然eqn与Su的量纲不一致。\n    eqn += Su + fvm::SuSp(Sp, psi);\n}\n\n```\n下面用一个例子来说明 `semiImplicitSource` 的作用。前提到 `scalarTransportFoam` 是使用 fvOptions 的一个最简单的求解器，这里对该求解器进一步简化，只保留瞬变项，对流和扩散项都删去，来验证 `semiImplicitSource` 的作用。 \n修改之后的 `TEqn` 为\n```\ntmp TEqn\n(\n    fvm::ddt(T)  == fvOptions(T)\n);\n```\n然后，`fvOptions` 词典文件的设置如下：\n```\n        firstHeatSource\n        {\n            type scalarSemiImplicitSource;\n            active          true;\n            selectionMode   cellZone;\n            cellZone        boxSourceZone;\n            scalarSemiImplicitSourceCoeffs \n            {\n                volumeMode absolute;\n                injectionRateSuSp \n                {\n                    T (0.05 0);\n                }\n            }\n        }\n```\n上述设置，相当于求解如下方程\n$$\n\\frac{\\partial T}{\\partial t}=S\\_u + S\\_p\\cdot T\n$$\n其中 $S\\_u=0.05, S\\_p = 0$ 。\n反观上面对代码的分析，可知对于当前的设置，$S\\_u$ 的量纲为 `TEqn` 的量纲除以体积量纲，$S\\_p$ 的量为 $S\\_u$ 量纲除以 `T` 的量纲，这与上面给出的方程是一致的。\n但是，要注意， `SemiImplicitSource` 有两个模式：absolute 和 specific，区别在于代码里的 `VDash` 的取值不一样。对于 absolute 模式，`VDash = V`，即所选的 cellZone 的体积；对于 specific 模式，`VDash = 1.0`。而代码里的 `Su` 和 `Sp` 的值都是用在`fvOptions` 词典文件设置的值除以 `VDash` 。\n所以，确切地说，求解的应该是如下积分方程：\n$$\n\\int\\_V \\frac{\\partial T}{\\partial t} dV - \\int\\_V \\nabla \\cdot (D\\_T \\nabla T) dV = \\int\\_V (\\frac{S\\_u}{V\\_{Dash}}+\\frac{S\\_p}{V\\_{Dash}}T )dV\n$$\n其中 $V\\_{Dash}$ 是选定的 cellZone 的体积。\n为了验证以上的推演，作了如下两个测试：\n+ 源项参数设置为`T (0.01 0)`，源项作用的区域为一个体积为$V\\_{Dash}=0.001$ 的 cellZone，为了消除热量传递，设置 $D\\_T = 0$，初始整个区域的 T 均为0， 模拟时间为1 s。\n按照上述的推演，如果是 `absolute` 模式，最终 1 s 时选定的 cellZone 的温度将是 $T=t\\cdot \\frac{S\\_u}{V\\_{Dash}}=1s\\cdot\\frac{0.01 k/s}{0.001}=10k$；如果是 `specific` 模式，那么最终1 s 时选定的 cellZone 的温度将是 $T=1s\\cdot\\frac{0.01 k/s}{1.0}=0.01k$。以上结果在测试算例中得到了证实。\n+ 源项参数设置为`T (1.0 2.0)`，同样设置 $D\\_T = 0$。这种情况下，可以先求微分方程 \n$$\\frac{\\partial T}{\\partial t}=x+yT$$\n的解，经简单计算得到 \n$$T=\\frac{e^{yt}\\cdot e^{yc}-x}{y}$$\n$c$ 为任意常数。\n若 $T|\\,_{t=0}=0k$，则可以得到定解为\n$$T=\\frac{e^{yt}\\cdot x-x}{y}$$\n若使用`specific` 模式，则根据当前的设置得到1 s时的解为\n$$T=\\frac{e^{2\\cdot 1}\\cdot 1 -1}{2}=3.194528 k$$\n算例测试结果为：\n+ 时间离散格式:Euler，$\\Delta T=0.001s$，$T=3.20193k$；\n+ 时间离散格式：Euler，$\\Delta T=0.0001s$，$T=3.19527k$，可见减小时间步后结果与解析解吻合度提高了很多。\n+ 时间离散格式：CrankNicolson，$\\Delta T=0.001s$，$T=3.19454k$。可见用高阶的时间离散格式，在同样时间步下能得到误差更小的结果。\n\n同样，如果用`absolute` 模式，且源项设置为 `T (0.001 0.002)` ，应该能得到一样的结果，实际上算例测试正是如此。由此可以认为推演得到了证实。\n\n至此，`SemiImplicitSource` 这个源项的核心部分就算是明了了。可是，现在测试的是非常简单的情形，如果在求解多个方程，且有多个方程里都加入了源项的情况下，该怎么给不同的方程设置不一样的源项呢？要解决这个问题，需要先理解清楚 `fvOptions` 的调用过程。\n\n\n##### fvOptions 源项的调用过程\n下面至下而上地来看看 fvOptions 的调用过程。\n\n`TEqn`里，有一个调用 `fvOptions` 的语句： `fvOptions(T)`，上一篇讲过， `fvOptions` 的定义为 \n```\nfv::IOoptionList fvOptions(mesh);\n```\n这就很明显了：建立一个 `IOoptionList` 类的对象 `fvOptions`。由此可知，求解器里的 `fvOptions` 是一个对象的名字，因此 `fvOptions(T)` 这种用法也只可能是对象调用类中重载的 `()` 运算符了。从前面的分析，可知 `IOoptionList` 本身很简单，仅是作为一个接口来用的，所以 `()` 与算符的重载要去其父类中去找。 `IOoptionList` 的作用是，从 \"constant\"（优先）或者 \"system\" 目录读取 `fvOptions` 文件，并作为 `IOobject` 类的对象传递给父类 `optionList` （从构造函数的成员初始化列表 `optionList(mesh, *this)` ）\n\n---\n\n接下来，就该进入 `optionList` 类了。这个类里，所有可能出现在求解器代码里函数都有了，包括 `correct` ， `constrain` ， `makeRelative` ， `makeAbsolute` ， `relative` 以及 `()` 运算符的重载。但是，注意这里并没有具体的代码实现，而是通过类似\n```cpp\nforAll(*this, i)\n{\n   this->operator[](i).makeAbsolute(phi);\n}\n```\n调用其他地方的函数。 `optionList` 的一个重要使命是，统计 `fvOptions` 文件里定义了多少个源项，并将每一个源项都作为一个储存起来，然后再根据词典的内容创建特定的源项。核心在于 `reset` 函数。为了说明这一点，先从构造函数看起\n```\nreset(optionsDict(dict));\n```\n可见构造函数里调用了 `reset` 函数，并且用 `optionsDict` 函数的返回值作为 `reset` 函数的参数。前文讲到， `IOoptionList` 类将 `fvOptions` 文件的内容以 `IOobject` 的形式传递给父类 `optionList`，所以，这里的参数 `dict` 可以理解为就是`fvOptions` 文件的内容。`optionsDict` 函数的代码如下：\n```\nconst Foam::dictionary& Foam::fv::optionList::optionsDict\n(\n    const dictionary& dict\n) const\n{\n    if (dict.found(\"options\"))\n    {\n        return dict.subDict(\"options\");\n    }\n    else\n    {\n        return dict;\n    }\n}\n```\n可见，这个函数去 `fvOptions` 文件里查找关键字 `options`，如果找到，就将 `options` 关键字对应的 subDict 内容返回，否则直接返回 `fvOptions` 文件的内容。举例说，形如\n```\nfirstHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.001 0);\n        }\n    }\n}\n```\n的，直接返回，因为这就构成了一个 dictionary；而形如\n```\noptions\n{\n    massSource1\n    {\n        type            scalarSemiImplicitSource;\n        $injector1;\n\n        scalarSemiImplicitSourceCoeffs\n        {\n            volumeMode      absolute;\n            injectionRateSuSp\n            {\n                thermo:rho.air     (1e-3 0); // kg/s\n            }\n        }\n    }\n\n    momentumSource1\n    {\n        type            vectorSemiImplicitSource;\n        $injector1;\n\n        vectorSemiImplicitSourceCoeffs\n        {\n            volumeMode      absolute;\n            injectionRateSuSp\n            {\n                U.air           ((0 -1e-2 0) 0); // kg*m/s^2\n            }\n        }\n    }\n\n    energySource1\n    {\n        type            scalarSemiImplicitSource;\n        $injector1;\n\n        scalarSemiImplicitSourceCoeffs\n        {\n            volumeMode      absolute;\n            injectionRateSuSp\n            {\n                e.air      (500 0); // kg*m^2/s^3\n            }\n        }\n    }\n}\n```\n的，则将options下的每一个 subDict 作为 dictionary 返回。注意，这里的 `dictionary` 类可以理解为一个容器，每一个\n```\nxxxxx\n{\n    ......\n}\n```\n都可以作为容器里的一个成员，容器的容量（size）等于总的成员数，每一个成员，其实就对应一个源项。\n于是，我们知道 `optionsDict` 返回了一个有一定数目成员的容器。再来看 `reset` 函数\n```cpp\nvoid Foam::fv::optionList::reset(const dictionary& dict)\n{\n    // Count number of active fvOptions\n    label count = 0;\n    \n    // 遍历 dict 容器，确定其成员的数目，即确定定义了几个源项。\n    forAllConstIter(dictionary, dict, iter) \n    {\n        if (iter().isDict())\n        {\n            count++;\n        }\n    }\n    this->setSize(count); // setSize 是 PtrList 类的成员，顾名思义，PtrList 是一个 List。PtrList<option> 类的成员是 options 类的对象。\n    \n    label i = 0;\n    // 遍历 dict 容器，根据每一个 dict 容器的成员来建立对应的 option 类的对象，这通过调用 option 类的 New 函数来实现。这是使用 RuntimeSelection 机制的类的很常规的做法。\n    forAllConstIter(dictionary, dict, iter)\n    {\n        if (iter().isDict())\n        {\n            const word& name = iter().keyword(); // keyword 返回的是类似 energySource1 这样的，是这个源项的一个名字\n            const dictionary& sourceDict = iter().dict();\n\n            this->set // set 函数，肯定毫无疑问也是从 PtrList 类中继承而来的\n            (\n                i++,\n                option::New(name, sourceDict, mesh_) // 调用 option 类的 New 函数\n            );\n        }\n    }\n}\n```\n注意，最重要的是， `reset` 里实现了对父类 `PtrList<option>` 的初始化。\n理解了这些，再来看 `correct` 以及 `()` 操作符重载中的代码，就好理解了：\n```cpp\n// 本来 *this 应该是 optionList 类的指针，这里先作个隐式转换，转成 PtrList<option> 类的指针。前面reset函数已经对 PtrList<option> 进行了初始化，使其读入了每一个源项。所以，这里的循环就是对每一个源项进行循环，然后调用对应的函数。operator[]肯定也是在PtrList类中定义的，i 指的是 PtrList 类的第 i 个成员，这里 PtrList 的每一个成员都是一个 option 类的对象，所以，makeAbsolute 函数是定义在 option 类中的函数。\nforAll(*this, i)\n{\n   this->operator[](i).makeAbsolute(phi);\n}\n```\n根据上面的理解，一个很自然的推论是，定义在 `fvOptions` 文件中的源项，其作用是叠加的。也就是说，上述的 `fvOptions(T)`，对定义在 `fvOptions` 文件中的每一个源项，都会调用一次。经测试， \n```\n firstHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.001 0);\n        }\n    }\n}\n        \nsecondHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.0 0.002);\n        }\n    }\n}\n```\n与\n```\nsecondHeatSource\n{\n    type scalarSemiImplicitSource;\n    active          true;\n    selectionMode   cellZone;\n    cellZone        boxSourceZone;\n    scalarSemiImplicitSourceCoeffs \n    {\n        volumeMode absolute;\n        injectionRateSuSp \n        {\n            T (0.0001 0.002);\n        }\n    }\n}\n```\n的作用是一样的，这证实了源项的作用确实是叠加的。\n此外，还要注意一点，那就是 `optionList` 类中重载的 `()` 运算符，其实是在调用 option 类中的 `addSup` 函数，并且其返回值是 fvMatrix 类的对象。\n```\ntemplate<class Type>\nFoam::tmp<Foam::fvMatrix<Type> > Foam::fv::optionList::operator()\n(\n    GeometricField<Type, fvPatchField, volMesh>& fld,\n    const word& fieldName\n)\n{\n    checkApplied();\n\n    const dimensionSet ds = fld.dimensions()/dimTime*dimVolume;\n\n    tmp<fvMatrix<Type> > tmtx(new fvMatrix<Type>(fld, ds));\n    fvMatrix<Type>& mtx = tmtx();\n\n    forAll(*this, i)\n    {\n        option& source = this->operator[](i);\n\n        label fieldI = source.applyToField(fieldName);\n\n        if (fieldI != -1)\n        {\n            source.setApplied(fieldI);\n\n            if (source.isActive())\n            {\n                if (debug)\n                {\n                    Info<< \"Applying source \" << source.name() << \" to field \"\n                        << fieldName << endl;\n                }\n\n                source.addSup(mtx, fieldI);\n            }\n        }\n    }\n    return tmtx;\n}\n```\n---\n\n理解了以上这些，就可以进入 `option` 类了。\noption 类是所有具体的源项类的基类，这个类里处理了所有源项都需要处理的部分，比如确定起始时间，选择源项作用的区域，这些都是在构造函数里完成的，主要是通过调用 `setSelection` 和 `setCellSet` 两个函数。这里需要注意的是数据成员 `cells_`， `V_`  以及 `fieldNames_`，分别定义源项作用区域的网格id （这里源项的作用区域是以 cell 为基础来指定的，即便是 points 模式，实际上源项作用的区域仍然是 points 所在的 cell。）， 选定区域的体积以及需要开启源项作用的场名（有时候，求解器的多个 Eqn 里有fvOptions，但是实际算例中只想针对特定的场开启源项，这可以通过指定 fieldNames_ 来实现）。\n\n注意 fieldNames 在 `option` 类中并没有初始化，需要在具体的源项类中指定。此外，`makeRelative` 等在求解器中实际调用的函数，在 `option` 类中也并没有进行具体的实现（但不是声明为纯虚函数，仅仅是函数体为空的而已，这里的结果不适合用纯虚函数）\n\n以 `SemiImplicitSource` 为例，主要去看 `addSup` 函数，这个函数，关键的一个参数是`fieldI`，这个参数，指的是 `fieldName_` 这个List的 `applyToField` 函数的返回值，用来判断一个 field 是否要启用源项。`fieldName_` 这个List，是在  `SemiImplicitSource` 类的 `setFieldData` 函数中赋值的，通过读取 `SemiImplicitSourceCoeffs` 中的参数来决定。但是这个 `fieldName_` 的确定方法根据不同的类有不同的做法，要具体分析。\n到此，`fvOpptions` 源项的调用途径就打通了，接下来可以继续具体分析特定的源项了。\n","slug":"fvOptions2","published":1,"updated":"2016-04-26T13:03:19.617Z","layout":"post","photos":[],"link":"","_id":"cioiqegd9002iz8mbkn0g25pt"},{"title":"fvOptions 浅析","date":"2016-03-20T07:24:30.000Z","comments":1,"_content":"\n本篇简单介绍 OpenFOAM 中的 fvOptions。按照[官方的介绍](http://www.openfoam.org/version2.2.0/fvOptions.php)，fvOptions 是一个可以在指定区域内添加源项或者其他约束（比如固定温度，或者多孔介质等）的框架。本篇对 fvOptions 框架的源码做一个浅析。\n\n<!--more-->\n\n先来了解一下 fvOptions 框架的结构，以及，在求解器中是怎么调用 fvOptions 的。为了避免问题复杂化，先从一个简单的求解器开始：`scalarTransportFoam`。OpenFOAM-2.3.1 中的 `scalarTransportFoam` 中已经引入了 `fvOptions` ，而更早的 OpenFOAM-2.1.1 中，则没有使用 `fvOptions` 。对比之下，很容易发现引入 `fvOptions` 其实就只涉及到两处代码修改：1. 增加了一个头文件 `createFvOptions.H`；2. 在 T 方程中增加了一项 `fvOptions(T)` 。 根据 OpenFOAM 的习惯，可以猜测这里增加的 `fvOptions(T)`，从C++的角度来看，多半是一个基类的对象 `fvOptions` 在调用类中重载过的 `()` 操作符。以上便是从这段简单的求解器代码中产生的对 `fvOptions` 的第一印象，下面来仔细看看 `fvOptions` 这个框架的结构。\n\n##### 1. createFvOptions.H\n既然求解器里只增加了这一个头文件，那就先从这个看起。这个文件位于 `src/fvOptions/include`，内容很简单，就一句话：\n```\nfv::IOoptionList fvOptions(mesh);\n```\n从这里就很清楚地可以看出， `scalarTransportFoam` 中 T 方程中增加的 `fvOptions` 是 `IOoptionList` 类的对象。\n\n##### 2. IOoptionList 类\n接下来看 `IOoptionList` 类。IOoptionList 类由两个文件组成： fvIOoptionList.H 和 fvIOoptionList.C。这里的目的在于了解 fvOptions 这个框架的结构，所以重点看头文件以及构造函数的定义。\n + fvIOoptionList.H \n```cpp\n#ifndef IOoptionList_H\n#define IOoptionList_H\n\n#include \"fvOptionList.H\"\n#include \"IOdictionary.H\"\n#include \"autoPtr.H\"\n\nnamespace Foam\n{\nnamespace fv\n{\n\nclass IOoptionList\n:\n    public IOdictionary,\n    public optionList\n{\nprivate:\n\n    // Private Member Functions\n\n        //- Create IO object if dictionary is present\n        IOobject createIOobject(const fvMesh& mesh) const;\n\n        //- Disallow default bitwise copy construct\n        IOoptionList(const IOoptionList&);\n\n        //- Disallow default bitwise assignment\n        void operator=(const IOoptionList&);\n\npublic:\n\n    // Constructors\n\n        //- Construct from components with list of field names\n        IOoptionList(const fvMesh& mesh);\n\n        //- Destructor\n        virtual ~IOoptionList()\n        {}\n\n    // Member Functions\n\n        //- Read dictionary\n        virtual bool read();\n};\n\n} // End namespace fv\n} // End namespace Foam\n#endif\n```\n从头文件可以看出， `IOoptionList` 类继承自 `IOdictionary` 和 `optionList` 类。根据对 OpenFOAM 的了解，`IOdictionary` 类是处理跟 IO 有关的，所以这里这个类很可能是用来处理 fvOptions 相关的字典文件的。真正涉及到具体的 fvOptions 源项的内容，应该是在 `optionList` 中有相关定义。\n\n+ 构造函数\n```\nFoam::fv::IOoptionList::IOoptionList\n(\n    const fvMesh& mesh\n)\n:\n    IOdictionary(createIOobject(mesh)), //构造函数里创建fvOptions字典文件\n    optionList(mesh, *this)\n{}\n```\n构造函数中调用 `createIOobject` 函数来对父类 `IOdictionary` 进行初始化，并将自己(`*this`) 作为参数传递给了父类 `optionList` 。\n\n+ createIOobject 函数\n构造函数中调用了 `createIOobject` 函数，而且这里的调用显然至关重要，所以需要看一下这个函数\n```\nFoam::IOobject Foam::fv::IOoptionList::createIOobject\n(\n    const fvMesh& mesh\n) const\n{\n    IOobject io\n    (\n        \"fvOptions\",\n        mesh.time().constant(),\n        mesh,\n        IOobject::MUST_READ,\n        IOobject::NO_WRITE\n    );\n\n    if (io.headerOk())\n    {\n        Info<< \"Creating finite volume options from \"\n            << io.instance()/io.name() << nl\n            << endl;\n\n        io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;\n        return io;\n    }\n    else\n    {\n        // Check if the fvOptions file is in system\n        io.instance() = mesh.time().system();\n\n        if (io.headerOk())\n        {\n            Info<< \"Creating finite volume options from \"\n                << io.instance()/io.name() << nl\n                << endl;\n\n            io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;\n            return io;\n        }\n        else\n        {\n            Info<< \"No finite volume options present\" << nl << endl;\n\n            io.readOpt() = IOobject::NO_READ;\n            return io;\n        }\n    }\n}\n```\n这个函数的意图就很明显了：创建了一个 `IOobject` 对象，并从 `system` 中读入文件 `fvOptions` 的内容来初始化该对象。并且，如果 `system` 目录下不存在 `fvOptions` 文件，那就尝试在 `constant` 下寻找。如果仍找不到，那就放弃读取。\n\n##### 3. optionList 类\n这个类由三个源文件定义： fvOptionList.H ， fvOptionList.C 和 fvOptionListTemplates.C。这里依然是重点关注头文件。\n+ fvOptionList.H\n```\n#ifndef optionList_H\n#define optionList_H\n\n#include \"PtrList.H\"\n#include \"GeometricField.H\"\n#include \"fvPatchField.H\"\n#include \"fvOption.H\"\n\nnamespace Foam\n{\nnamespace fv\n{\n\nclass optionList\n:\n    public PtrList<option>\n{\nprotected:\n\n    ......\n\n        const dictionary& optionsDict(const dictionary& dict) const;\n\n        bool readOptions(const dictionary& dict);\n\npublic:\n\n    // Constructors\n        optionList(const fvMesh& mesh);\n\n        optionList(const fvMesh& mesh, const dictionary& dict);\n\n\n        void reset(const dictionary& dict);\n\n\t......\n\n            //- Return source for equation\n            // 重载的括号操作符，在求解器里，方程的构造过程中调用的就是这些括号操作符。\n            template<class Type>\n            tmp<fvMatrix<Type> > operator()\n            (\n                const volScalarField& rho,\n                GeometricField<Type, fvPatchField, volMesh>& fld\n            );\n\n\t......\n};\n\n} // End namespace fv\n} // End namespace Foam\n\n#ifdef NoRepository\n    #include \"fvOptionListTemplates.C\"\n#endif\n\n#endif\n```\n这里注意两点，一是 `optionList` 类继承自 `PtrList<option>` ，另一个是，这里出现了重载的运算符 `()` 。继承自 `PtrList<option>` 意味着很可能 fvOptions 类是支持同时定义多个源项的。\n\n+ 构造函数\n```\nFoam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)\n:\n    PtrList<option>(),\n    mesh_(mesh),\n    checkTimeIndex_(mesh_.time().startTimeIndex() + 2)\n{\n    reset(optionsDict(dict));\n}\n```\n构造函数里，调用了 `reSet` 和 `optionsDict` 函数。下面看一下这两个函数的作用：\n\n+ optionsDict 函数\n```\nconst Foam::dictionary& Foam::fv::optionList::optionsDict\n(\n    const dictionary& dict\n) const\n{\n    if (dict.found(\"options\"))\n    {\n        return dict.subDict(\"options\");\n    }\n    else\n    {\n        return dict;\n    }\n}\n```\n这个函数需要一个参数 ` const dictionary& dict`，这个参数，显然是从构造函数的 `const dictionary& dict` ，再回顾一下上文中，`IOoptionList` 中将自己本身（*this）传递给父类 `optionsDict` ，而且， `IOoptionList` 同时也继承自 `IOdictionary` 类，并且将从文件 `fvOptions` 类中读取的内容来对其父类 `IOdictionary` 类。所以这里不难理解，参数 `const dictionary& dict` 其实就是文件 `fvOptions` 的内容。这里需要从文件中 `fvOptions` 寻找关键字 `options` ，如果找到，那就返回 `options` 所指定的 `subDict` ，否则就直接返回 `fvOptions` 本身。\n\n+ reset 函数\n再来看 reset 函数：\n```\nvoid Foam::fv::optionList::reset(const dictionary& dict)\n{\n    // Count number of active fvOptions\n    label count = 0;\n    forAllConstIter(dictionary, dict, iter)\n    {\n        if (iter().isDict())\n        {\n            count++;\n        }\n    }\n\n    this->setSize(count);\n    label i = 0;\n    forAllConstIter(dictionary, dict, iter)\n    {\n        if (iter().isDict())\n        {\n            const word& name = iter().keyword();\n            const dictionary& sourceDict = iter().dict();\n\n            this->set\n            (\n                i++,\n                option::New(name, sourceDict, mesh_)\n            );\n        }\n    }\n}\n```\n注意在构造函数里，这个函数的参数是 `optionsDict` 类的返回值。这里，函数是在统计 `fvOptions` 中有效的 `options` 的个数，并根据每一个有效的 `options` ，调用 `option` 类的 `New` 函数（看到 New 函数，很自然就会想到 Run Time Selection 吧！）来构造对象指针，且将该指针存到 `PtrList` 类定义的 List 里。 这里更是直接能看出来，在文件 `fvOptions` 中，是可以同时定义多个源项的。 \n\n##### 4. option 类\n再继续看 `option` 类。`option` 类由四个源文件定义： `fvOption.H` ， `fvOption.C` ， `fvOptionI.H` 和 `fvOptionIO.C` 。\n\n+ fvOption.H\n```\n#ifndef fvOption_H\n#define fvOption_H\n\n#include \"fvMatricesFwd.H\"\n#include \"volFieldsFwd.H\"\n#include \"cellSet.H\"\n#include \"autoPtr.H\"\n#include \"meshToMesh.H\"\n\n#include \"runTimeSelectionTables.H\"\n\nnamespace Foam\n{\nclass fvMesh；\nnamespace fv\n{\n\nclass option\n{\npublic:\n\n    // Public data\n\n        //- Enumeration for selection mode types\n        enum selectionModeType\n        {\n            smPoints,\n            smCellSet,\n            smCellZone,\n            smMapRegion,\n            smAll\n        };\n\n......\n```\n\n `option` 类终于不再继承自其他类了。而且，包含的头文件中有 `#include \"runTimeSelectionTables.H\"` 可以猜想，这个类肯定是作为接口的基类来使用的。\n经验证，具体的源项类，如 `semiImplicitSource`，都是继承自 `option` 类的。当然，这里会用到 RTS 机制来提供灵活地源项选择。和其他的作为接口使用的基类类似， `option` 类中定义了所有的具体源项类可能用到的控制选项，比如，区域的选择（Points，cellSet，cellZone，mapRegion，all），开始时间（timeStart），持续时间（duration）激活开关（active）以及各个可能调用到的函数。\n\n至此，fvOptions 框架的结构就大体理清了，总结如下图：\n\n\n![fvOptions 框架的结构](/image/fvOptions/fvOptions.png)\n\n\n##### 参考\n1. http://www.sourceflux.de/blog/series/fvoptions/\n2. http://www.openfoam.org/version2.2.0/fvOptions.php\n","source":"_posts/fvOptions1.md","raw":"title: \"fvOptions 浅析\"\ndate: 2016-03-20 15:24:30\ncomments: true\ntags:\n- fvOptions\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n本篇简单介绍 OpenFOAM 中的 fvOptions。按照[官方的介绍](http://www.openfoam.org/version2.2.0/fvOptions.php)，fvOptions 是一个可以在指定区域内添加源项或者其他约束（比如固定温度，或者多孔介质等）的框架。本篇对 fvOptions 框架的源码做一个浅析。\n\n<!--more-->\n\n先来了解一下 fvOptions 框架的结构，以及，在求解器中是怎么调用 fvOptions 的。为了避免问题复杂化，先从一个简单的求解器开始：`scalarTransportFoam`。OpenFOAM-2.3.1 中的 `scalarTransportFoam` 中已经引入了 `fvOptions` ，而更早的 OpenFOAM-2.1.1 中，则没有使用 `fvOptions` 。对比之下，很容易发现引入 `fvOptions` 其实就只涉及到两处代码修改：1. 增加了一个头文件 `createFvOptions.H`；2. 在 T 方程中增加了一项 `fvOptions(T)` 。 根据 OpenFOAM 的习惯，可以猜测这里增加的 `fvOptions(T)`，从C++的角度来看，多半是一个基类的对象 `fvOptions` 在调用类中重载过的 `()` 操作符。以上便是从这段简单的求解器代码中产生的对 `fvOptions` 的第一印象，下面来仔细看看 `fvOptions` 这个框架的结构。\n\n##### 1. createFvOptions.H\n既然求解器里只增加了这一个头文件，那就先从这个看起。这个文件位于 `src/fvOptions/include`，内容很简单，就一句话：\n```\nfv::IOoptionList fvOptions(mesh);\n```\n从这里就很清楚地可以看出， `scalarTransportFoam` 中 T 方程中增加的 `fvOptions` 是 `IOoptionList` 类的对象。\n\n##### 2. IOoptionList 类\n接下来看 `IOoptionList` 类。IOoptionList 类由两个文件组成： fvIOoptionList.H 和 fvIOoptionList.C。这里的目的在于了解 fvOptions 这个框架的结构，所以重点看头文件以及构造函数的定义。\n + fvIOoptionList.H \n```cpp\n#ifndef IOoptionList_H\n#define IOoptionList_H\n\n#include \"fvOptionList.H\"\n#include \"IOdictionary.H\"\n#include \"autoPtr.H\"\n\nnamespace Foam\n{\nnamespace fv\n{\n\nclass IOoptionList\n:\n    public IOdictionary,\n    public optionList\n{\nprivate:\n\n    // Private Member Functions\n\n        //- Create IO object if dictionary is present\n        IOobject createIOobject(const fvMesh& mesh) const;\n\n        //- Disallow default bitwise copy construct\n        IOoptionList(const IOoptionList&);\n\n        //- Disallow default bitwise assignment\n        void operator=(const IOoptionList&);\n\npublic:\n\n    // Constructors\n\n        //- Construct from components with list of field names\n        IOoptionList(const fvMesh& mesh);\n\n        //- Destructor\n        virtual ~IOoptionList()\n        {}\n\n    // Member Functions\n\n        //- Read dictionary\n        virtual bool read();\n};\n\n} // End namespace fv\n} // End namespace Foam\n#endif\n```\n从头文件可以看出， `IOoptionList` 类继承自 `IOdictionary` 和 `optionList` 类。根据对 OpenFOAM 的了解，`IOdictionary` 类是处理跟 IO 有关的，所以这里这个类很可能是用来处理 fvOptions 相关的字典文件的。真正涉及到具体的 fvOptions 源项的内容，应该是在 `optionList` 中有相关定义。\n\n+ 构造函数\n```\nFoam::fv::IOoptionList::IOoptionList\n(\n    const fvMesh& mesh\n)\n:\n    IOdictionary(createIOobject(mesh)), //构造函数里创建fvOptions字典文件\n    optionList(mesh, *this)\n{}\n```\n构造函数中调用 `createIOobject` 函数来对父类 `IOdictionary` 进行初始化，并将自己(`*this`) 作为参数传递给了父类 `optionList` 。\n\n+ createIOobject 函数\n构造函数中调用了 `createIOobject` 函数，而且这里的调用显然至关重要，所以需要看一下这个函数\n```\nFoam::IOobject Foam::fv::IOoptionList::createIOobject\n(\n    const fvMesh& mesh\n) const\n{\n    IOobject io\n    (\n        \"fvOptions\",\n        mesh.time().constant(),\n        mesh,\n        IOobject::MUST_READ,\n        IOobject::NO_WRITE\n    );\n\n    if (io.headerOk())\n    {\n        Info<< \"Creating finite volume options from \"\n            << io.instance()/io.name() << nl\n            << endl;\n\n        io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;\n        return io;\n    }\n    else\n    {\n        // Check if the fvOptions file is in system\n        io.instance() = mesh.time().system();\n\n        if (io.headerOk())\n        {\n            Info<< \"Creating finite volume options from \"\n                << io.instance()/io.name() << nl\n                << endl;\n\n            io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;\n            return io;\n        }\n        else\n        {\n            Info<< \"No finite volume options present\" << nl << endl;\n\n            io.readOpt() = IOobject::NO_READ;\n            return io;\n        }\n    }\n}\n```\n这个函数的意图就很明显了：创建了一个 `IOobject` 对象，并从 `system` 中读入文件 `fvOptions` 的内容来初始化该对象。并且，如果 `system` 目录下不存在 `fvOptions` 文件，那就尝试在 `constant` 下寻找。如果仍找不到，那就放弃读取。\n\n##### 3. optionList 类\n这个类由三个源文件定义： fvOptionList.H ， fvOptionList.C 和 fvOptionListTemplates.C。这里依然是重点关注头文件。\n+ fvOptionList.H\n```\n#ifndef optionList_H\n#define optionList_H\n\n#include \"PtrList.H\"\n#include \"GeometricField.H\"\n#include \"fvPatchField.H\"\n#include \"fvOption.H\"\n\nnamespace Foam\n{\nnamespace fv\n{\n\nclass optionList\n:\n    public PtrList<option>\n{\nprotected:\n\n    ......\n\n        const dictionary& optionsDict(const dictionary& dict) const;\n\n        bool readOptions(const dictionary& dict);\n\npublic:\n\n    // Constructors\n        optionList(const fvMesh& mesh);\n\n        optionList(const fvMesh& mesh, const dictionary& dict);\n\n\n        void reset(const dictionary& dict);\n\n\t......\n\n            //- Return source for equation\n            // 重载的括号操作符，在求解器里，方程的构造过程中调用的就是这些括号操作符。\n            template<class Type>\n            tmp<fvMatrix<Type> > operator()\n            (\n                const volScalarField& rho,\n                GeometricField<Type, fvPatchField, volMesh>& fld\n            );\n\n\t......\n};\n\n} // End namespace fv\n} // End namespace Foam\n\n#ifdef NoRepository\n    #include \"fvOptionListTemplates.C\"\n#endif\n\n#endif\n```\n这里注意两点，一是 `optionList` 类继承自 `PtrList<option>` ，另一个是，这里出现了重载的运算符 `()` 。继承自 `PtrList<option>` 意味着很可能 fvOptions 类是支持同时定义多个源项的。\n\n+ 构造函数\n```\nFoam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)\n:\n    PtrList<option>(),\n    mesh_(mesh),\n    checkTimeIndex_(mesh_.time().startTimeIndex() + 2)\n{\n    reset(optionsDict(dict));\n}\n```\n构造函数里，调用了 `reSet` 和 `optionsDict` 函数。下面看一下这两个函数的作用：\n\n+ optionsDict 函数\n```\nconst Foam::dictionary& Foam::fv::optionList::optionsDict\n(\n    const dictionary& dict\n) const\n{\n    if (dict.found(\"options\"))\n    {\n        return dict.subDict(\"options\");\n    }\n    else\n    {\n        return dict;\n    }\n}\n```\n这个函数需要一个参数 ` const dictionary& dict`，这个参数，显然是从构造函数的 `const dictionary& dict` ，再回顾一下上文中，`IOoptionList` 中将自己本身（*this）传递给父类 `optionsDict` ，而且， `IOoptionList` 同时也继承自 `IOdictionary` 类，并且将从文件 `fvOptions` 类中读取的内容来对其父类 `IOdictionary` 类。所以这里不难理解，参数 `const dictionary& dict` 其实就是文件 `fvOptions` 的内容。这里需要从文件中 `fvOptions` 寻找关键字 `options` ，如果找到，那就返回 `options` 所指定的 `subDict` ，否则就直接返回 `fvOptions` 本身。\n\n+ reset 函数\n再来看 reset 函数：\n```\nvoid Foam::fv::optionList::reset(const dictionary& dict)\n{\n    // Count number of active fvOptions\n    label count = 0;\n    forAllConstIter(dictionary, dict, iter)\n    {\n        if (iter().isDict())\n        {\n            count++;\n        }\n    }\n\n    this->setSize(count);\n    label i = 0;\n    forAllConstIter(dictionary, dict, iter)\n    {\n        if (iter().isDict())\n        {\n            const word& name = iter().keyword();\n            const dictionary& sourceDict = iter().dict();\n\n            this->set\n            (\n                i++,\n                option::New(name, sourceDict, mesh_)\n            );\n        }\n    }\n}\n```\n注意在构造函数里，这个函数的参数是 `optionsDict` 类的返回值。这里，函数是在统计 `fvOptions` 中有效的 `options` 的个数，并根据每一个有效的 `options` ，调用 `option` 类的 `New` 函数（看到 New 函数，很自然就会想到 Run Time Selection 吧！）来构造对象指针，且将该指针存到 `PtrList` 类定义的 List 里。 这里更是直接能看出来，在文件 `fvOptions` 中，是可以同时定义多个源项的。 \n\n##### 4. option 类\n再继续看 `option` 类。`option` 类由四个源文件定义： `fvOption.H` ， `fvOption.C` ， `fvOptionI.H` 和 `fvOptionIO.C` 。\n\n+ fvOption.H\n```\n#ifndef fvOption_H\n#define fvOption_H\n\n#include \"fvMatricesFwd.H\"\n#include \"volFieldsFwd.H\"\n#include \"cellSet.H\"\n#include \"autoPtr.H\"\n#include \"meshToMesh.H\"\n\n#include \"runTimeSelectionTables.H\"\n\nnamespace Foam\n{\nclass fvMesh；\nnamespace fv\n{\n\nclass option\n{\npublic:\n\n    // Public data\n\n        //- Enumeration for selection mode types\n        enum selectionModeType\n        {\n            smPoints,\n            smCellSet,\n            smCellZone,\n            smMapRegion,\n            smAll\n        };\n\n......\n```\n\n `option` 类终于不再继承自其他类了。而且，包含的头文件中有 `#include \"runTimeSelectionTables.H\"` 可以猜想，这个类肯定是作为接口的基类来使用的。\n经验证，具体的源项类，如 `semiImplicitSource`，都是继承自 `option` 类的。当然，这里会用到 RTS 机制来提供灵活地源项选择。和其他的作为接口使用的基类类似， `option` 类中定义了所有的具体源项类可能用到的控制选项，比如，区域的选择（Points，cellSet，cellZone，mapRegion，all），开始时间（timeStart），持续时间（duration）激活开关（active）以及各个可能调用到的函数。\n\n至此，fvOptions 框架的结构就大体理清了，总结如下图：\n\n\n![fvOptions 框架的结构](/image/fvOptions/fvOptions.png)\n\n\n##### 参考\n1. http://www.sourceflux.de/blog/series/fvoptions/\n2. http://www.openfoam.org/version2.2.0/fvOptions.php\n","slug":"fvOptions1","published":1,"updated":"2016-05-04T02:51:09.504Z","layout":"post","photos":[],"link":"","_id":"cioiqegdf002nz8mbwwual2be"},{"title":"利用functionObjects对指定区域内进行后处理","date":"2015-05-09T07:24:56.000Z","comments":1,"_content":"\nCFD中很重要的一个环节是模拟结果的后处理。而后处理过程中，常常涉及到对某个指定区域的某个物理量进行操作，比如，求指定截面上的流率，或者求某个区域内的平均空隙率，等等。这里介绍一种利用 OpenFOAM 中的 `functionObjects` 来对指定区域进行后处理的方法。本方法一共分三步：1). 将指定区域内的网格(或者面)提取到 cellZone（或faceZone）; 2). 在 controlDict 里写后处理 functions；3). 运行后处理。\n<!--more-->\n\n### 1. 将指定区域的网格(或面)提取到 cellZone (或faceZone)\n这一步有很多方法可以实现，这里介绍用`setSet`结合`setsToZones`的方法。\n#### 1.1 `setSet`的基本用法\n`setSet`是 OpenFOAM 提供的一个用于生成网格集合(cellSet)、面集合（faceSet）以及点集合（pointSet）的交互式工具，终端里运行`setSet`，便进入交互模式：\n```\n$ setSet\n\n/*---------------------------------------------------------------------------*\\\n| =========                 |                                                 |\n| \\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |\n|  \\\\    /   O peration     | Version:  2.3.1                                 |\n|   \\\\  /    A nd           | Web:      www.OpenFOAM.org                      |\n|    \\\\/     M anipulation  |                                                 |\n\\*---------------------------------------------------------------------------*/\nBuild  : 2.3.1-bcfaaa7b8660\nExec   : setSet\nDate   : May 09 2015\nTime   : 13:50:44\nHost   : \"xxxxx\"\nPID    : 11255\nCase   : /home/xxxxx/OpenFOAM/xxxx-2.3.1/run/volField/cavity\nnProcs : 1\nsigFpe : Enabling floating point exception trapping (FOAM_SIGFPE).\nfileModificationChecking : Monitoring run-time modified files using timeStampMaster\nallowSystemOperations : Allowing user-supplied system call operations\n\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\nCreate time\n\nCreate polyMesh for time = 0\n\nTime:0  cells:9  faces:42  points:32  patches:3  bb:(0 0 0) (0.1 0.1 0.01)\n\nSuccessfully read history from .setSet\nTime = 0\n    mesh not changed.\nPlease type 'help', 'quit' or a set command after prompt.\nreadline>\n\n```\n`setSet`的基本语法是：`cellSet|faceSet|pointSet <setName> <action> <source>`。第一个字段，是选择需要建立的是哪一类集合(cellSet|faceSet|pointSet)，第二个字段是`setName`，顾名思义，给集合取个名字，第三个字段是`action`，即具体的操作，比如`new`(新建)，`add`(增加）等，最后一个字段是`source`，即指定cell(face,point)的来源，比如有一种是`boxToCell`，这种来源要求定义一个box，并将中心在这个box里的网格当作操作的对象。下面举几个例子：\n\n- `faceSet f0 new boxToFace (0 0 0) (1 1 1)`: 建立一个新的面集f0，并将面心落在对角线顶点分别为(0 0 0)和(1 1 1)的立方体中的面提取出来放到该集合中;\n- `cellSet f0 new boxToCell (0 0 0) (1 1 1)`:建立一个新的网格集f0，并将面心落在对角线顶点分别为(0 0 0)和(1 1 1)的立方体中的网格提取出来放到该集合中;\n- `faceSet f0 new patchToFace movingWall`:新建一个面集f0，并将边界`movingWall`的所有面放到该集合中；\n- ` cellSet c0 new faceToCell f0 any`: 新建一个网格集合c0，并将面集f0中所有的面对应的网格放到网格集合c0中；\n- `cellSet c0 add pointToCell p0 any`，将点集p0中所有的点对应的网格添加到**已经存在**的网格集c0中。\n\n如果要一次建立很多个集合，那么可以将建立规则写在一个文本文件里，每个规则一行，写好后的文件大致是这样\n```\nfaceSet f0 new boxToFace (0 0 0) (1 1 1)\nfaceSet f1 new boxToFace (1 1 1) (2 2 2)\ncellSet c0 new faceToCell f0 any\ncellSet c1 new faceToCell f1 any\n```\n然后运行命令\n```\nsetSet -batch <filename>\n```\n进行批处理。但是这样会针对每一个时间步都运行一次，如果只想运行一次，可以指定时间\n```\nsetSet -batch <filename> -time 0\n或者\nsetSet -batch <filename> -latestTime\n```\n运行结束以后，程序会将sets信息放在`polyMesh/sets`目录下，同时生成相应的vtk文件在算例根目录下的VTK目录里，方便在paraview中查看。\n关于`setSet`更详细的信息可以参考`setSet`提供的 help（终端里运行`setSet`进入交互模式以后输入`help`）。\n#### 1.2 setsToZones\n这一步非常简单，只需要在终端里运行\n```\nsetsToZones\n```\n就可以将前面建立好的 cellSet(faceSet,pointSet)转换成 cellZone(faceZone,pointZone)。\n\n其实除了`setSet`和`setsToZones`结合的方法，还可以用`topoSet`来生成cellZone，本篇不详述了，可以参考 [OpenFOAMWiki](https://openfoamwiki.net/index.php/TopoSet)。\n### 2. 在 controlDict 里写 functions\n有了前面建立好的cellZone (faceZone,pointZone) 以后，就可以在 controlDict 写 functions 来对指定的区域进行后处理了。funtions 的基本写法是在 controlDict 文件的最后，添加类似如下的信息：\n```\nfunctions\n(\n   cell0\n   {\n     type cellSource; // 指定操作区域的类型，cellsource 表示是操作区域是一个基于网格(cell)的，比如cellZone。\n     functionObjectLibs // 指定需要加载的动态库\n     (\n         //\"libsimpleFunctionObjects.so\" // swak4Foam 提供的一个函数库\n         \"libfieldFunctionObjects.so\"  // OpenFOAM自带的一个函数库\n     );\n     verbose true; //是否要在终端里输出程序运行过程\n     //outputControl timeStep; // 每一个时间步都运行\n     outputControl outputTime; // 只在需要输出的时间步才运行，参考controlDict的 writeControl和writeInterval\n     log           true; // 是否生成 log\n     valueOutput   true; // 是否需要在每一个时间步对应的数据文件夹里(0.1 0.2 之类的) 将指定 source 的值输出来。\n     source        cellZone; //指定操作区域的具体的组成，这里cellZone表示操作区域是由一个cellZone组成的。\n     sourceName    f0; // cellZone 的名字\n     operation     volAverage; //操作方法，这里是体积平均\n     fields // 需要进行操作的物理量\n     (\n         T\n     );\n   }\n);\n```\n注意这里的结构，\n```\nfunctions\n(\n  ...\n);\n```\n是最外一层，包括在其中的就是各种功能的 `functionObjects`了。\n```\ncell0\n{\n ...\n}\n```\n代表的是一个`functionObject`，其中`cell0`是该`functionObject`的名字。里面内容的含义参照上面示例中的注释。如果需要写多个`functionObjects`，那只需要依次都写下来就好了，注意`functionObjects`的名字不能重复，结构如下\n```\nfunctions\n(\n   cell0\n   {\n   ...\n   }\n   \n   cell1\n   {\n    ...\n   }\n);\n```\n\n### 3. 运行后处理\n写好`functions`以后，就可以运行后处理了。在终端里运行\n```\n execFlowFunctionObjects \n```\n就能执行定义在controlDict里的后处理函数了。\n有时候，可能会报错，说找不到`phi`，那这时可以加上`-noFlow`选项\n```\n execFlowFunctionObjects  -noFlow\n```\n当然，时间相关的选项也是可以用的\n```\n execFlowFunctionObjects  -noFlow -time 3\n execFlowFunctionObjects  -noFlow -latestTime\n```\n注意，本篇强调的是后处理，即算例运行完以后对数据进行的处理。实际上，熟悉OpenFOAM都知道，写在 controlDict 的functions是会随着算例的运行而同时运行的，所以，如果是运行一个新算例，那么你也完全可以事先规划好后处理相关的操作，然后在controlDict写好functions，这样等算例运行结束，所需要的后处理结果也同时生成了。\n\n有人会问，我怎么知道那些 type，source，operation 等有哪些选项可用呢？这里介绍一种的方法，即所谓的[香蕉大法](https://openfoamwiki.net/index.php/OpenFOAM_guide/Use_bananas)。比如对于 type，你不知道有哪些可用，那么将type设置为 banana  (将上面第2节示例的中 `type cellSource`改成`type banana`)，然后运行`execFlowFunctionObjects`，这时，会得到如下信息：\n```\n--> FOAM FATAL ERROR: \nUnknown function type banana\nValid functions are : \n14\n(\ncellSource\nfaceSource\nfieldAverage\nfieldCoordinateSystemTransform\nfieldMinMax\ninitSwakFunctionObject\nnearWallFields\npatchProbes\nprobes\nreadFields\nsets\nstreamLine\nsurfaceInterpolateFields\nsurfaces\n)\n```\n这回就知道了所有可用选项了吧。对于 source 和 operation 也可以同样的方法得到所有可用的选项。注意，source和operation的可用选项，是随着type的不同而不同的，这里就不详述了。\nP.S：banana不是OpenFOAM定义的特殊字符串，改成任意非有效的字符串，效果都一样。\n\n最后，上面只是介绍了一些一般性的原则，如果读者想参考一些实际的例子，可以去 OpenFOAM 的 tutorials 里挖掘，这里给一个找出 OpenFOAM 中有哪些算例运用了 functions 的方法，供大家参考。\n\n```\nfind $WM_PROJECT_DIR -name controlDict | xargs grep \"functions\"\n```\n在我的电脑上，运行结果如下：\n```\n/opt/openfoam231/src/postProcessing/functionObjects/systemCall/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/utilities/timeActivatedFileUpdate/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/fieldAverage/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/nearWallFields/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/fieldValues/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/streamLine/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/fieldMinMax/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/wallBoundedStreamLine/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/IO/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleFoam/TJunctionFan/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleFoam/channel395/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleFoam/TJunction/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/pitzDailyMapped/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/pitzDaily/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/motorBike/lesFiles/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/motorBike/motorBike/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleDyMFoam/wingMotion/wingMotion2D_simpleFoam/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleDyMFoam/propeller/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleDyMFoam/movingCone/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/simpleFoam/pitzDaily/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/simpleFoam/pitzDailyExptInlet/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/simpleFoam/motorBike/system/controlDict:functions\n/opt/openfoam231/tutorials/lagrangian/reactingParcelFoam/verticalChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/lagrangian/LTSReactingParcelFoam/verticalChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/lagrangian/simpleReactingParcelFoam/verticalChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interFoam/ras/waterChannel/LTSInterFoam/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interFoam/ras/waterChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank3D3DoF/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank3D6DoF/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank2D3DoF/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/DTCHull/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank3D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank2D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/laminar/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/laminar/bubbleColumnIATE/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/laminar/fluidisedBed/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/LES/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/RAS/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/RAS/fluidisedBed/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/potentialFreeSurfaceDyMFoam/oscillatingBox/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/LTSInterFoam/DTCHull/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/cavitatingFoam/les/throttle3D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/cavitatingFoam/les/throttle/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/compressibleInterDyMFoam/ras/sloshingTank2D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/potentialFreeSurfaceFoam/oscillatingBox/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/multiphaseEulerFoam/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/freeSpaceStream/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/wedge15Ma5/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/supersonicCorner/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/freeSpacePeriodic/system/controlDict:functions\n/opt/openfoam231/tutorials/basic/potentialFoam/cylinder/system/controlDict:functions\n/opt/openfoam231/tutorials/compressible/rhoPimpleFoam/les/pitzDaily/system/controlDict:functions\n/opt/openfoam231/tutorials/compressible/sonicFoam/ras/nacaAirfoil/system/controlDict:functions\n```\n","source":"_posts/functionObjects.md","raw":"title: 利用functionObjects对指定区域内进行后处理\ndate: 2015-05-09 15:24:56\ncomments: true\ntags:\n - OpenFOAM\n - Postprocessing\ncategories:\n - OpenFOAM\n---\n\nCFD中很重要的一个环节是模拟结果的后处理。而后处理过程中，常常涉及到对某个指定区域的某个物理量进行操作，比如，求指定截面上的流率，或者求某个区域内的平均空隙率，等等。这里介绍一种利用 OpenFOAM 中的 `functionObjects` 来对指定区域进行后处理的方法。本方法一共分三步：1). 将指定区域内的网格(或者面)提取到 cellZone（或faceZone）; 2). 在 controlDict 里写后处理 functions；3). 运行后处理。\n<!--more-->\n\n### 1. 将指定区域的网格(或面)提取到 cellZone (或faceZone)\n这一步有很多方法可以实现，这里介绍用`setSet`结合`setsToZones`的方法。\n#### 1.1 `setSet`的基本用法\n`setSet`是 OpenFOAM 提供的一个用于生成网格集合(cellSet)、面集合（faceSet）以及点集合（pointSet）的交互式工具，终端里运行`setSet`，便进入交互模式：\n```\n$ setSet\n\n/*---------------------------------------------------------------------------*\\\n| =========                 |                                                 |\n| \\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |\n|  \\\\    /   O peration     | Version:  2.3.1                                 |\n|   \\\\  /    A nd           | Web:      www.OpenFOAM.org                      |\n|    \\\\/     M anipulation  |                                                 |\n\\*---------------------------------------------------------------------------*/\nBuild  : 2.3.1-bcfaaa7b8660\nExec   : setSet\nDate   : May 09 2015\nTime   : 13:50:44\nHost   : \"xxxxx\"\nPID    : 11255\nCase   : /home/xxxxx/OpenFOAM/xxxx-2.3.1/run/volField/cavity\nnProcs : 1\nsigFpe : Enabling floating point exception trapping (FOAM_SIGFPE).\nfileModificationChecking : Monitoring run-time modified files using timeStampMaster\nallowSystemOperations : Allowing user-supplied system call operations\n\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\nCreate time\n\nCreate polyMesh for time = 0\n\nTime:0  cells:9  faces:42  points:32  patches:3  bb:(0 0 0) (0.1 0.1 0.01)\n\nSuccessfully read history from .setSet\nTime = 0\n    mesh not changed.\nPlease type 'help', 'quit' or a set command after prompt.\nreadline>\n\n```\n`setSet`的基本语法是：`cellSet|faceSet|pointSet <setName> <action> <source>`。第一个字段，是选择需要建立的是哪一类集合(cellSet|faceSet|pointSet)，第二个字段是`setName`，顾名思义，给集合取个名字，第三个字段是`action`，即具体的操作，比如`new`(新建)，`add`(增加）等，最后一个字段是`source`，即指定cell(face,point)的来源，比如有一种是`boxToCell`，这种来源要求定义一个box，并将中心在这个box里的网格当作操作的对象。下面举几个例子：\n\n- `faceSet f0 new boxToFace (0 0 0) (1 1 1)`: 建立一个新的面集f0，并将面心落在对角线顶点分别为(0 0 0)和(1 1 1)的立方体中的面提取出来放到该集合中;\n- `cellSet f0 new boxToCell (0 0 0) (1 1 1)`:建立一个新的网格集f0，并将面心落在对角线顶点分别为(0 0 0)和(1 1 1)的立方体中的网格提取出来放到该集合中;\n- `faceSet f0 new patchToFace movingWall`:新建一个面集f0，并将边界`movingWall`的所有面放到该集合中；\n- ` cellSet c0 new faceToCell f0 any`: 新建一个网格集合c0，并将面集f0中所有的面对应的网格放到网格集合c0中；\n- `cellSet c0 add pointToCell p0 any`，将点集p0中所有的点对应的网格添加到**已经存在**的网格集c0中。\n\n如果要一次建立很多个集合，那么可以将建立规则写在一个文本文件里，每个规则一行，写好后的文件大致是这样\n```\nfaceSet f0 new boxToFace (0 0 0) (1 1 1)\nfaceSet f1 new boxToFace (1 1 1) (2 2 2)\ncellSet c0 new faceToCell f0 any\ncellSet c1 new faceToCell f1 any\n```\n然后运行命令\n```\nsetSet -batch <filename>\n```\n进行批处理。但是这样会针对每一个时间步都运行一次，如果只想运行一次，可以指定时间\n```\nsetSet -batch <filename> -time 0\n或者\nsetSet -batch <filename> -latestTime\n```\n运行结束以后，程序会将sets信息放在`polyMesh/sets`目录下，同时生成相应的vtk文件在算例根目录下的VTK目录里，方便在paraview中查看。\n关于`setSet`更详细的信息可以参考`setSet`提供的 help（终端里运行`setSet`进入交互模式以后输入`help`）。\n#### 1.2 setsToZones\n这一步非常简单，只需要在终端里运行\n```\nsetsToZones\n```\n就可以将前面建立好的 cellSet(faceSet,pointSet)转换成 cellZone(faceZone,pointZone)。\n\n其实除了`setSet`和`setsToZones`结合的方法，还可以用`topoSet`来生成cellZone，本篇不详述了，可以参考 [OpenFOAMWiki](https://openfoamwiki.net/index.php/TopoSet)。\n### 2. 在 controlDict 里写 functions\n有了前面建立好的cellZone (faceZone,pointZone) 以后，就可以在 controlDict 写 functions 来对指定的区域进行后处理了。funtions 的基本写法是在 controlDict 文件的最后，添加类似如下的信息：\n```\nfunctions\n(\n   cell0\n   {\n     type cellSource; // 指定操作区域的类型，cellsource 表示是操作区域是一个基于网格(cell)的，比如cellZone。\n     functionObjectLibs // 指定需要加载的动态库\n     (\n         //\"libsimpleFunctionObjects.so\" // swak4Foam 提供的一个函数库\n         \"libfieldFunctionObjects.so\"  // OpenFOAM自带的一个函数库\n     );\n     verbose true; //是否要在终端里输出程序运行过程\n     //outputControl timeStep; // 每一个时间步都运行\n     outputControl outputTime; // 只在需要输出的时间步才运行，参考controlDict的 writeControl和writeInterval\n     log           true; // 是否生成 log\n     valueOutput   true; // 是否需要在每一个时间步对应的数据文件夹里(0.1 0.2 之类的) 将指定 source 的值输出来。\n     source        cellZone; //指定操作区域的具体的组成，这里cellZone表示操作区域是由一个cellZone组成的。\n     sourceName    f0; // cellZone 的名字\n     operation     volAverage; //操作方法，这里是体积平均\n     fields // 需要进行操作的物理量\n     (\n         T\n     );\n   }\n);\n```\n注意这里的结构，\n```\nfunctions\n(\n  ...\n);\n```\n是最外一层，包括在其中的就是各种功能的 `functionObjects`了。\n```\ncell0\n{\n ...\n}\n```\n代表的是一个`functionObject`，其中`cell0`是该`functionObject`的名字。里面内容的含义参照上面示例中的注释。如果需要写多个`functionObjects`，那只需要依次都写下来就好了，注意`functionObjects`的名字不能重复，结构如下\n```\nfunctions\n(\n   cell0\n   {\n   ...\n   }\n   \n   cell1\n   {\n    ...\n   }\n);\n```\n\n### 3. 运行后处理\n写好`functions`以后，就可以运行后处理了。在终端里运行\n```\n execFlowFunctionObjects \n```\n就能执行定义在controlDict里的后处理函数了。\n有时候，可能会报错，说找不到`phi`，那这时可以加上`-noFlow`选项\n```\n execFlowFunctionObjects  -noFlow\n```\n当然，时间相关的选项也是可以用的\n```\n execFlowFunctionObjects  -noFlow -time 3\n execFlowFunctionObjects  -noFlow -latestTime\n```\n注意，本篇强调的是后处理，即算例运行完以后对数据进行的处理。实际上，熟悉OpenFOAM都知道，写在 controlDict 的functions是会随着算例的运行而同时运行的，所以，如果是运行一个新算例，那么你也完全可以事先规划好后处理相关的操作，然后在controlDict写好functions，这样等算例运行结束，所需要的后处理结果也同时生成了。\n\n有人会问，我怎么知道那些 type，source，operation 等有哪些选项可用呢？这里介绍一种的方法，即所谓的[香蕉大法](https://openfoamwiki.net/index.php/OpenFOAM_guide/Use_bananas)。比如对于 type，你不知道有哪些可用，那么将type设置为 banana  (将上面第2节示例的中 `type cellSource`改成`type banana`)，然后运行`execFlowFunctionObjects`，这时，会得到如下信息：\n```\n--> FOAM FATAL ERROR: \nUnknown function type banana\nValid functions are : \n14\n(\ncellSource\nfaceSource\nfieldAverage\nfieldCoordinateSystemTransform\nfieldMinMax\ninitSwakFunctionObject\nnearWallFields\npatchProbes\nprobes\nreadFields\nsets\nstreamLine\nsurfaceInterpolateFields\nsurfaces\n)\n```\n这回就知道了所有可用选项了吧。对于 source 和 operation 也可以同样的方法得到所有可用的选项。注意，source和operation的可用选项，是随着type的不同而不同的，这里就不详述了。\nP.S：banana不是OpenFOAM定义的特殊字符串，改成任意非有效的字符串，效果都一样。\n\n最后，上面只是介绍了一些一般性的原则，如果读者想参考一些实际的例子，可以去 OpenFOAM 的 tutorials 里挖掘，这里给一个找出 OpenFOAM 中有哪些算例运用了 functions 的方法，供大家参考。\n\n```\nfind $WM_PROJECT_DIR -name controlDict | xargs grep \"functions\"\n```\n在我的电脑上，运行结果如下：\n```\n/opt/openfoam231/src/postProcessing/functionObjects/systemCall/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/utilities/timeActivatedFileUpdate/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/fieldAverage/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/nearWallFields/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/fieldValues/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/streamLine/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/fieldMinMax/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/field/wallBoundedStreamLine/controlDict:functions\n/opt/openfoam231/src/postProcessing/functionObjects/IO/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleFoam/TJunctionFan/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleFoam/channel395/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleFoam/TJunction/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/pitzDailyMapped/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/pitzDaily/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/motorBike/lesFiles/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pisoFoam/les/motorBike/motorBike/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleDyMFoam/wingMotion/wingMotion2D_simpleFoam/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleDyMFoam/propeller/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/pimpleDyMFoam/movingCone/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/simpleFoam/pitzDaily/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/simpleFoam/pitzDailyExptInlet/system/controlDict:functions\n/opt/openfoam231/tutorials/incompressible/simpleFoam/motorBike/system/controlDict:functions\n/opt/openfoam231/tutorials/lagrangian/reactingParcelFoam/verticalChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/lagrangian/LTSReactingParcelFoam/verticalChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/lagrangian/simpleReactingParcelFoam/verticalChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interFoam/ras/waterChannel/LTSInterFoam/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interFoam/ras/waterChannel/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank3D3DoF/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank3D6DoF/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank2D3DoF/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/DTCHull/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank3D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/interDyMFoam/ras/sloshingTank2D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/laminar/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/laminar/bubbleColumnIATE/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/laminar/fluidisedBed/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/LES/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/RAS/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/twoPhaseEulerFoam/RAS/fluidisedBed/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/potentialFreeSurfaceDyMFoam/oscillatingBox/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/LTSInterFoam/DTCHull/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/cavitatingFoam/les/throttle3D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/cavitatingFoam/les/throttle/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/compressibleInterDyMFoam/ras/sloshingTank2D/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/potentialFreeSurfaceFoam/oscillatingBox/system/controlDict:functions\n/opt/openfoam231/tutorials/multiphase/multiphaseEulerFoam/bubbleColumn/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/freeSpaceStream/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/wedge15Ma5/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/supersonicCorner/system/controlDict:functions\n/opt/openfoam231/tutorials/discreteMethods/dsmcFoam/freeSpacePeriodic/system/controlDict:functions\n/opt/openfoam231/tutorials/basic/potentialFoam/cylinder/system/controlDict:functions\n/opt/openfoam231/tutorials/compressible/rhoPimpleFoam/les/pitzDaily/system/controlDict:functions\n/opt/openfoam231/tutorials/compressible/sonicFoam/ras/nacaAirfoil/system/controlDict:functions\n```\n","slug":"functionObjects","published":1,"updated":"2015-05-09T07:42:05.792Z","layout":"post","photos":[],"link":"","_id":"cioiqegdi002rz8mbgpk2lx0k"},{"title":"foamTimeAverage","date":"2015-05-31T09:11:00.000Z","comments":1,"_content":"\n 在[前面的一篇博文](http://xiaopingqiu.github.io/2015/04/12/fieldAverage/)中，我介绍了`fieldAverage`这个`functionObject`的用法，其中提到， 可以用`window`这个参数来控制所计算的时均值的时间范围。如果 `base = time`且`window = 10`，那从第10s以后，每个时刻 t 输出的时均值其实相当于从 t-10 到 t 这个时间段内的时均值。但是，根据分析可以发现，这个时均值并不严格等价于从 t-10 时刻到 t 时刻某个场的时均值。有时候，需要从某个时刻才开始计算时均值，而`fieldAverage`没有参数可以控制从某个时刻才开始计算时间平均。于是我参照`OpenFOAM`的`patchAverage.C`的代码写了一个后处理程序，用来计算指定时间段内的某个场的时均值。\n \n <!--more-->\n\n### foamTimeAverage 简介\n `foamTimeAverage` 是一段简单的后处理程序，其功能是在算例运行结束以后，根据指定的时间段，从数据文件夹里循环读入指定时间段内指定场的数据，并计算该段时间的该指定场的时均值，结果将会输出到该时间段最后一个时刻的数据文件夹内。举例说：\n ```\nfoamTimeAverage p -time 0.4:0.5\n\n ```\n 将会计算`0.4 s-0.5 s`时间段内`p`的时均值，结果将输出到`0.5`内，时均值文件名为`p_mean`。这个程序只支持计算`volField`的时均值，不支持`surfaceField`。目前，这个程序只在`OpenFOAM-2.1.1`和`OpenFOAM-2.3.1`下通过可编译测试，运行的结果目前只在`OpenFOAM-2.1.1`下对`volScalarField`和`volVectorField`进行了测试。理论上讲，应该在其他版本的`OpenFOAM`下也可以正常编译和运行。\n\n 以下是`foamTimeAverage`的代码：\n ```\n/*---------------------------------------------------------------------------*\\\n *=========                 |\n *\\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox\n *\\\\     /   O peration     |\n *\\\\    /    A nd           | Copyright (C) 1991-2009 OpenCFD Ltd.\n *\\\\   /     M anipulation  |\n * -------------------------------------------------------------------------------\n license\n This file is part of OpenFOAM.\n\n OpenFOAM is free software; you can redistribute it and/or modify it\n under the terms of the GNU General Public License as published by the\n Free Software Foundation; either version 2 of the License, or (at your\n option) any later version.\n\n OpenFOAM is distributed in the hope that it will be useful, but WITHOUT\n ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n for more details.\n\n You should have received a copy of the GNU General Public License\n along with OpenFOAM; if not, write to the Free Software Foundation,\n Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\n\nApplication\n   foamTimeAverage\n\nAuthor\n  Xiaoping Qiu \n  q.giskard@gmail.com\n\nDescription\nCalculates the time average  of the specified volField over the specified time range.\n\n\\*---------------------------------------------------------------------------*/\n\n#include \"fvCFD.H\"\n\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\ntemplate<class FieldType>\nvoid calcTimeAverage\n(\n    fvMesh& mesh,\n    const IOobject& fieldHeader,\n    const word& fieldName,\n    Time& runTime,\n    instantList& timeDirs,\n    bool& done\n)\n{\n    label nfield = 0;\n    const word meanFieldName = fieldName + \"_mean\"; \n\n    //Info << \"class name = \" << fieldHeader.headerClassName() << endl;\n    //Info << \"typeName = \" << FieldType::typeName << endl;\n    if(!done && fieldHeader.headerClassName() == FieldType::typeName)\n    {\n\tFieldType dummy\n\t    (\n\t\tIOobject\n\t\t(\n\t\t    fieldName,\n\t\t    runTime.timeName(),\n\t\t    mesh,\n\t\t    IOobject::MUST_READ\n\t\t),\n\t\tmesh\n\t    );\n\n\tFieldType meanField\n\t    (\n\t\tIOobject\n\t\t(\n\t\t    meanFieldName,\n\t\t    runTime.timeName(),\n\t\t    mesh,\n\t\t    IOobject::NO_READ\n\t\t),\n\t\tdummy\n\t    );\n\n\tmeanField *= scalar(0.0);\n\n\tforAll(timeDirs, timeI)\n\t{\n\t    runTime.setTime(timeDirs[timeI], timeI);\n\t    Info << \"Time = \" << runTime.timeName() <<endl;\n\n\t    IOobject io\n\t\t(\n\t\t    fieldName,\n\t\t    runTime.timeName(),\n\t\t    mesh,\n\t\t    IOobject::MUST_READ\n\t\t);\n\n\t    if (io.headerOk())\n\t    {\n\t\tmesh.readUpdate();\n\n\t\tif(!done && io.headerClassName() == FieldType::typeName)\n\t\t{\n\t\t    Info << \"   Reading \" << io.headerClassName() << \" \" <<io.name() << endl;\n\n\t\t    FieldType field(io, mesh);\n\n\t\t    meanField += field;\n\t\t    nfield++;\n\t\t}\n\t    }\n\t    else\n\t    {\n\t\tInfo << \"   No Field \" << fieldName << endl; \n\t    }\n\t}\n\n\tif(nfield > 0)\n\t{\n\t    Info << \"number of field = \" << nfield << endl;\n\t    meanField /= nfield;\n\t}\n\n\tInfo<< \"writing to timeDir \" << runTime.timeName()  << endl;\n\tmeanField.write();\n\tdone = true;\n\n    }\n}\n// Main program:\n\nint main(int argc, char *argv[])\n{\n\n    Foam::timeSelector::addOptions();\n    #include \"addRegionOption.H\"\n    Foam::argList::validArgs.append(\"fieldName\");\n\n#   include \"setRootCase.H\"\n#   include \"createTime.H\"\n    instantList timeDirs = timeSelector::select0(runTime, args);\n    runTime.setTime(timeDirs[0], 0);\n#   include \"createNamedMesh.H\"\n\n    // get filename from command line\n    const word fieldName = args[1];\n    bool done = false;\n\n    IOobject fieldHeader\n\t(\n\t    fieldName,\n\t    runTime.timeName(),\n\t    mesh,\n\t    IOobject::MUST_READ\n\t);\n    if(fieldHeader.headerOk())//very important!\n    {\n\tcalcTimeAverage<volScalarField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volVectorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volTensorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volSymmTensorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volSphericalTensorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n    }\n    else\n    {\n\tInfo<< \" Error! No field \" << fieldName << endl;\n    }\n\n    Info<< \"End\\n\" << endl;\n\n    return 0;\n}\n\n// ************************************************************************* //\n\n ```\n\n 更详细的参考，包括编译和使用方法，见[代码的github页面](https://github.com/xiaopingqiu/foamTimeAverage)。\n\n###  一点说明\n `foamTimeAverage` 这个程序有一个缺点，那就是只能根据输出的文件来计算时均值。举例说，假如算例的时间步长取`0.0001 s`，每`0.01 s` 输出一次，那`0.4 s-0.5 s`这个时间段的时均值就只涉及到`0.40s, 0.41s, 0.42s, ... 0.50s`这些时刻的值。有时候，如果想计算的是更精确的时均值，即`0.4000s, 0.4001s, 0.4002s ... 0.5000s`这些时刻的时均值，该怎么办呢？我没有实际测试过，但有一种方法我觉得可行，需要结合`fieldAverage`和`foamTimeAverage`来实现，具体操作如下：\n 1. 设置`fieldAverage`，注意将`resetOnOutput`设置为`true`，`outputControl`设置为`outputTime`，`base`设为`time`，`window`不需要设置，这样，应该就会在每一个输出的时间文件夹里输出一个时均值，这个时均值计算的上一次输出到下一次输出之间的时间段内的时均值，用上面的例子来说，即`0.5`文件夹内输出的`p`的时均值`pMean`是`0.4001s, 0.4002s, ... 0.5000s`这些时刻的`p`的时均值。\n\n 2. 用`foamTimeAverage`计算指定时间段内`pMean`的时均值。举例说，\n  `foamTimeAverage pMean -time 0.04:0.05` 计算的将是 `0.3001s, 0.3002s, ... 0.5000s`这些时刻的时均值，这是因为，0.04 文件夹内的`pMean`是 `0.3001s, 0.3002s, ... 0.4000s`的时均值，0.05 文件夹内的`pMean`是`0.4001s, 0.4002s, ... 0.5000s`时刻内的时均值。\n \n  用以上方法就可以实现计算指定时间段内同时精度更高的时均值了。**再次申明一下，上述方法仅仅是我根据原理进行的推演，没有经过检验**。\n","source":"_posts/foamTimeAverage.md","raw":"title: \"foamTimeAverage\"\ndate: 2015-05-31 17:11:00\ncomments: true\ntags:\n - OpenFOAM\n - Postprocessing\ncategories:\n - OpenFOAM\n---\n\n 在[前面的一篇博文](http://xiaopingqiu.github.io/2015/04/12/fieldAverage/)中，我介绍了`fieldAverage`这个`functionObject`的用法，其中提到， 可以用`window`这个参数来控制所计算的时均值的时间范围。如果 `base = time`且`window = 10`，那从第10s以后，每个时刻 t 输出的时均值其实相当于从 t-10 到 t 这个时间段内的时均值。但是，根据分析可以发现，这个时均值并不严格等价于从 t-10 时刻到 t 时刻某个场的时均值。有时候，需要从某个时刻才开始计算时均值，而`fieldAverage`没有参数可以控制从某个时刻才开始计算时间平均。于是我参照`OpenFOAM`的`patchAverage.C`的代码写了一个后处理程序，用来计算指定时间段内的某个场的时均值。\n \n <!--more-->\n\n### foamTimeAverage 简介\n `foamTimeAverage` 是一段简单的后处理程序，其功能是在算例运行结束以后，根据指定的时间段，从数据文件夹里循环读入指定时间段内指定场的数据，并计算该段时间的该指定场的时均值，结果将会输出到该时间段最后一个时刻的数据文件夹内。举例说：\n ```\nfoamTimeAverage p -time 0.4:0.5\n\n ```\n 将会计算`0.4 s-0.5 s`时间段内`p`的时均值，结果将输出到`0.5`内，时均值文件名为`p_mean`。这个程序只支持计算`volField`的时均值，不支持`surfaceField`。目前，这个程序只在`OpenFOAM-2.1.1`和`OpenFOAM-2.3.1`下通过可编译测试，运行的结果目前只在`OpenFOAM-2.1.1`下对`volScalarField`和`volVectorField`进行了测试。理论上讲，应该在其他版本的`OpenFOAM`下也可以正常编译和运行。\n\n 以下是`foamTimeAverage`的代码：\n ```\n/*---------------------------------------------------------------------------*\\\n *=========                 |\n *\\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox\n *\\\\     /   O peration     |\n *\\\\    /    A nd           | Copyright (C) 1991-2009 OpenCFD Ltd.\n *\\\\   /     M anipulation  |\n * -------------------------------------------------------------------------------\n license\n This file is part of OpenFOAM.\n\n OpenFOAM is free software; you can redistribute it and/or modify it\n under the terms of the GNU General Public License as published by the\n Free Software Foundation; either version 2 of the License, or (at your\n option) any later version.\n\n OpenFOAM is distributed in the hope that it will be useful, but WITHOUT\n ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n for more details.\n\n You should have received a copy of the GNU General Public License\n along with OpenFOAM; if not, write to the Free Software Foundation,\n Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\n\nApplication\n   foamTimeAverage\n\nAuthor\n  Xiaoping Qiu \n  q.giskard@gmail.com\n\nDescription\nCalculates the time average  of the specified volField over the specified time range.\n\n\\*---------------------------------------------------------------------------*/\n\n#include \"fvCFD.H\"\n\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\ntemplate<class FieldType>\nvoid calcTimeAverage\n(\n    fvMesh& mesh,\n    const IOobject& fieldHeader,\n    const word& fieldName,\n    Time& runTime,\n    instantList& timeDirs,\n    bool& done\n)\n{\n    label nfield = 0;\n    const word meanFieldName = fieldName + \"_mean\"; \n\n    //Info << \"class name = \" << fieldHeader.headerClassName() << endl;\n    //Info << \"typeName = \" << FieldType::typeName << endl;\n    if(!done && fieldHeader.headerClassName() == FieldType::typeName)\n    {\n\tFieldType dummy\n\t    (\n\t\tIOobject\n\t\t(\n\t\t    fieldName,\n\t\t    runTime.timeName(),\n\t\t    mesh,\n\t\t    IOobject::MUST_READ\n\t\t),\n\t\tmesh\n\t    );\n\n\tFieldType meanField\n\t    (\n\t\tIOobject\n\t\t(\n\t\t    meanFieldName,\n\t\t    runTime.timeName(),\n\t\t    mesh,\n\t\t    IOobject::NO_READ\n\t\t),\n\t\tdummy\n\t    );\n\n\tmeanField *= scalar(0.0);\n\n\tforAll(timeDirs, timeI)\n\t{\n\t    runTime.setTime(timeDirs[timeI], timeI);\n\t    Info << \"Time = \" << runTime.timeName() <<endl;\n\n\t    IOobject io\n\t\t(\n\t\t    fieldName,\n\t\t    runTime.timeName(),\n\t\t    mesh,\n\t\t    IOobject::MUST_READ\n\t\t);\n\n\t    if (io.headerOk())\n\t    {\n\t\tmesh.readUpdate();\n\n\t\tif(!done && io.headerClassName() == FieldType::typeName)\n\t\t{\n\t\t    Info << \"   Reading \" << io.headerClassName() << \" \" <<io.name() << endl;\n\n\t\t    FieldType field(io, mesh);\n\n\t\t    meanField += field;\n\t\t    nfield++;\n\t\t}\n\t    }\n\t    else\n\t    {\n\t\tInfo << \"   No Field \" << fieldName << endl; \n\t    }\n\t}\n\n\tif(nfield > 0)\n\t{\n\t    Info << \"number of field = \" << nfield << endl;\n\t    meanField /= nfield;\n\t}\n\n\tInfo<< \"writing to timeDir \" << runTime.timeName()  << endl;\n\tmeanField.write();\n\tdone = true;\n\n    }\n}\n// Main program:\n\nint main(int argc, char *argv[])\n{\n\n    Foam::timeSelector::addOptions();\n    #include \"addRegionOption.H\"\n    Foam::argList::validArgs.append(\"fieldName\");\n\n#   include \"setRootCase.H\"\n#   include \"createTime.H\"\n    instantList timeDirs = timeSelector::select0(runTime, args);\n    runTime.setTime(timeDirs[0], 0);\n#   include \"createNamedMesh.H\"\n\n    // get filename from command line\n    const word fieldName = args[1];\n    bool done = false;\n\n    IOobject fieldHeader\n\t(\n\t    fieldName,\n\t    runTime.timeName(),\n\t    mesh,\n\t    IOobject::MUST_READ\n\t);\n    if(fieldHeader.headerOk())//very important!\n    {\n\tcalcTimeAverage<volScalarField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volVectorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volTensorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volSymmTensorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n\tcalcTimeAverage<volSphericalTensorField>(mesh, fieldHeader, fieldName, runTime, timeDirs, done);\n    }\n    else\n    {\n\tInfo<< \" Error! No field \" << fieldName << endl;\n    }\n\n    Info<< \"End\\n\" << endl;\n\n    return 0;\n}\n\n// ************************************************************************* //\n\n ```\n\n 更详细的参考，包括编译和使用方法，见[代码的github页面](https://github.com/xiaopingqiu/foamTimeAverage)。\n\n###  一点说明\n `foamTimeAverage` 这个程序有一个缺点，那就是只能根据输出的文件来计算时均值。举例说，假如算例的时间步长取`0.0001 s`，每`0.01 s` 输出一次，那`0.4 s-0.5 s`这个时间段的时均值就只涉及到`0.40s, 0.41s, 0.42s, ... 0.50s`这些时刻的值。有时候，如果想计算的是更精确的时均值，即`0.4000s, 0.4001s, 0.4002s ... 0.5000s`这些时刻的时均值，该怎么办呢？我没有实际测试过，但有一种方法我觉得可行，需要结合`fieldAverage`和`foamTimeAverage`来实现，具体操作如下：\n 1. 设置`fieldAverage`，注意将`resetOnOutput`设置为`true`，`outputControl`设置为`outputTime`，`base`设为`time`，`window`不需要设置，这样，应该就会在每一个输出的时间文件夹里输出一个时均值，这个时均值计算的上一次输出到下一次输出之间的时间段内的时均值，用上面的例子来说，即`0.5`文件夹内输出的`p`的时均值`pMean`是`0.4001s, 0.4002s, ... 0.5000s`这些时刻的`p`的时均值。\n\n 2. 用`foamTimeAverage`计算指定时间段内`pMean`的时均值。举例说，\n  `foamTimeAverage pMean -time 0.04:0.05` 计算的将是 `0.3001s, 0.3002s, ... 0.5000s`这些时刻的时均值，这是因为，0.04 文件夹内的`pMean`是 `0.3001s, 0.3002s, ... 0.4000s`的时均值，0.05 文件夹内的`pMean`是`0.4001s, 0.4002s, ... 0.5000s`时刻内的时均值。\n \n  用以上方法就可以实现计算指定时间段内同时精度更高的时均值了。**再次申明一下，上述方法仅仅是我根据原理进行的推演，没有经过检验**。\n","slug":"foamTimeAverage","published":1,"updated":"2015-06-04T01:27:01.949Z","layout":"post","photos":[],"link":"","_id":"cioiqegdl002vz8mb32uz20uy"},{"title":"fieldAverage 使用说明","date":"2015-04-12T09:00:54.000Z","comments":1,"_content":"\n`fieldAverage`是 OpenFOAM 中的一种 functionObject，用来计算时均值。其基本用法是作为一个 function object 放在 controlDict 文件中，运行 solver 的同时计算指定场的时均值，以下是一个示例：\n\n```\nfunctions\n{\n    fieldAverage1\n    {\n        type            fieldAverage;\n        functionObjectLibs ( \"libfieldFunctionObjects.so\" );\n        outputControl   outputTime;\n        fields\n        (\n            Ua\n            {\n                 mean        on;\n                 prime2Mean  off;\n                 base        time;\n            }\n            Ub\n            {\n                 mean        on;\n                 prime2Mean  off;\n                 base        time;\n            }\n        );\n    }\n}\n```\n<!--more-->\n\n但是，在有效地使用`fieldAverage`之前，有一些问题需要澄清，其中最重要的一个是：**`fieldAverage`到底在对哪个时间段求的时间平均**？本文通过对`fieldAverage`的源码进行分析，试图厘清这些细节，并给出一个可靠的`fieldAverage`使用说明。涉及到的源文件包括：`fieldAverage.H`, `fieldAverage.C`, `fieldAverageTemplates.C`, `fieldAverageItem.C`，位于目录`$WM_PROJECT_DIR/src/postProcessing/functionObjects/field/fieldAverage`下。\n#####首要问题：**`fieldAverage`对哪个时间区间进行时均计算**？\n上面这段代码是从我运行的一个算例的 controlDict 中摘出来的。算例运行过程中发现，每一个时间步都要调用计算时均值相关的代码，并屏幕上的输出也会打印出`Calculating averages`，而每一个输出数据文件里，都有`*Mean`数据，其中`*`表示在`controlDict`中定义了需要求解时均值的某个场。\n经过摸索发现，跟时均值求解密切相关的一段代码位于[`fieldAverageTemplates.C`](http://foam.sourceforge.net/docs/cpp/a08780_source.html)，下面是从中摘出来的一段核心代码：\n```c++\n182 template<class Type>\n183 void Foam::fieldAverage::calculateMeanFieldType(const label fieldI) const\n184 {\n185  const word& fieldName = faItems_[fieldI].fieldName();\n187  if (obr_.foundObject<Type>(fieldName))\n188  {\n189    const Type& baseField = obr_.lookupObject<Type>(fieldName);\n190 \n191    Type& meanField = const_cast<Type&>\n192    (\n193    obr_.lookupObject<Type>(faItems_[fieldI].meanFieldName())\n194    );\n195 \n196    scalar dt = obr_.time().deltaTValue();\n197    scalar Dt = totalTime_[fieldI];\n198 \n199    if (faItems_[fieldI].iterBase())\n200    {\n201      dt = 1.0;\n202      Dt = scalar(totalIter_[fieldI]);\n203    }\n204 \n205    scalar alpha = (Dt - dt)/Dt;\n206    scalar beta = dt/Dt;\n207 \n208    if (faItems_[fieldI].window() > 0)\n209    {\n210      const scalar w = faItems_[fieldI].window();\n211 \n212      if (Dt - dt >= w)\n213      {\n214        alpha = (w - dt)/w;\n215        beta = dt/w;\n216      }\n217    }\n219    meanField = alpha*meanField + beta*baseField;\n220  }\n221 }\n\n```\n以下是我根据理解整理的代码解析：\n  1. 变量`baseField`定义为当前时间步计算得到的场的值(Ua)，`meanField`定义为上一个时间步对应的场的时均值(UaMean)。\n  2. `dt`定义为时间步长，`Dt`定义为当前所在的时间。注意199行，`iterBase()`这个函数定义为当`base` (见controlDict) 为`ITER`时，返回`true`。此时，`dt`定义为1，意义很显然，当以时间步数为基准的时候，下一步和上一步当然是只相差1个时间步；`Dt`则被定义为当前所在的时间步(第xx步)。所以，`Dt`和`dt`的真实含义取决于`base`。**`fieldAverageItem.C`中给`base`赋了默认值为`ITER`**。`base`可以指定为`time`，具体见下文。\n  3. alpha 定义为(Dt-dt)/Dt, beta 定义为 dt/Dt。然后当前时间步的`meanField`定义为`alpha*meanField + beta*baseField`。举例说明，假设dt = 1, Dt = 20, 假设上一时间步的时均值为`meanField_pre`，则新时刻时均值为\n   ```\n    19/20*meanField_pre + 1/20*baseField) = (19*meanField_pre + baseField)/20\n   ```\n  `19*meanField_pre`的含义很明显，前19步的时均值乘以总时间步数，也就是前 19 步 Field 值的加和，再加上当前时间步的值`baseField`，就是前 20 步的 Field 值的和。用这个值除以20，得到新时刻的时均值`meanField`。由此可见，在这种情况下，**每个时间步输出的时均值是从开始时刻到当前时间步的Field值对总时间的平均**。\n  4. 很多时候，我们并不想得到从开始到结束整个模拟时段内的平均值，而希望得到指定时段内的平均值，这时，可以通过指定`window`变量的值来达到目的。代码208行，注意到这里出现了一个新函数`window`。可以查到`window`函数返回值默认是`-1`。但可以在controlDict中`base`下面定义变量`windows`的值，当`window`函数返回值大于0时，208-217将被执行。接着上一点的假设，dt = 1, 定义并初始化为`window`函数的返回值，假设为20。则： \n    + 当 `Dt` 小于或等于20时，不满足`Dt - dt >= w`，此时时均值将按照上一点叙述的进行计算。\n    + 当 `Dt`大于20以后，假设`Dt = 21`，此时 `alpha = (w-dt)/w = (20-1)/20`，`bata = dt/w = 1/19`。令`meanField_pre`为`Dt = 20` 时的时均值，则`Dt = 21`时的时均值\n    ```\n    meanField = 19/20 * meanField_pre + 1/20 * baseField = (19 * meanField_pre + baseField)/20\n    ```\n    对比发现，如果`window = -1`(默认值)，则`Dt = 21`的时均值应该是`20/21 * meanField_pre + 1/21 * baseField`。而定义了`windows = 20`以后，此处的`19 * meanField_pre` 可以理解为`Dt = 21`这一步之前的19步的Field值的和，加上`baseField`，则为`Dt = 21` 以及其之前19步，共20步的Field值的和。再除以20，则为 `Dt = 21 `以及其之前19步这20步的时均值。**所以，当设定`window`为一正整数`w`时，输出的时均值是当前时间步以及其之前`w-1`步，这`w`步内Field的时均值**。\n     \n附上一个更一般化的示例：\n```\nfieldAverage1\n{\n    type fieldAverage;\n    functionObjectLibs (\"libfieldFunctionObjects.so\");\n    resetOnRestart true;\n    resetOnOutput false;\n    startTime     0.1; // 开始计算时均值的时间\n    endTime       1.0; // 停止计算时均值的时间\n    outputControl   outputTime;\n    fields\n    (\n        U\n        {\n            mean            on;\n            prime2Mean      on;\n            base            time; //以物理时间为基础来计算平均，而不是时间步数。\n            window          10.0;\n            windowName      w1; //optional\n        }\n        p\n        {\n            mean            on;\n            prime2Mean      on;\n            base            time;\n        }\n    );\n}\n\n```\n`resetOnRestart`的值决定当solver继续运行时，是否要读取最近一个时间步的`meanField`的值来计算接下来时刻的时均值；`resetOnOutput`，顾名思义，是否要在每一次输出到文件以后重置`meanField`的值。这两个开关的默认值都是`false`。\n`mean`这个开关的含义无需多言，计算公式如下：\n$$ \\overline {x} = \\frac{1}{N} \\sum \\limits \\_{i=0}^{N} x\\_i $$\n`prime2Mean`的计算公式如下：\n$$ \\overline{x'}^{\\, 2} = \\frac{1}{N}\\displaystyle\\sum\\limits\\_{i=0}^N (x\\_i - \\overline{x})^2 $$\n所以，如果`prime2Mean`为`on`，`mean`必须为`on`。\n\n\n此外，如果计算已经结束，controlDict 中定义的 function 仍可以用`execFlowFunctionObjects`来执行。只是，这样的运行只能利用输出到文件的数据来进行计算了。举例说，假如时间步长是 0.001s， 每 0.1s 输出一次，那么同样是1-2s的时均值，solver运行过程中求解的公式是:\n```\n( field.at(1.001) +field.at(1.002) + ... + field.at(2.0) )/1000\n```\n而solver运行完以后利用`execFlowFunctionObjects`计算的时均值应该是：\n```\n( field.at(1.1) + field.at(1.2) + ... + field.at(2) )/10\n```\n注意，这个结果没有经过直接的验证，是我根据原理推演的结果。\n有时候，运行`execFlowFunctionObjects`会报错说找不到`phi`，这时加上`-noFlow`选项，就不会报错了。\n\n##### 小结\n1. `base`用来指定作时间平均的基础，是基于时间步数(ITER)还是物理时间(time);\n2. `window`用来作平均的时间段的长度，如果不设定，则求的是从开始到当前时间这个时间段的平均值。`window`的数值的实际含义依`base`而定，如果`base`是`ITER`，则`window=20`表示当前步及其前 19 个时间步从 20 个时间步内的平均，而如果`base`是`time`,则表示的是 20s 内的平均。\n\n**错误更正**：\n2015.09.05: 在 controlDict 里， `base` 只能是  `time` 或 `iteration`，而不能是上文中说的 `ITER`， `ITER` 只是代码中使用的一个代称 。感谢“启之” 指出！  \n\n**补充**:\n2015.11.26：上文没有提 `prime2Mean` 的计算方法，今天仔细看了一下，记录如下。\n `prime2Mean` 的公式上面提到了，计算代码需要结合三个个函数来看，分别是 fieldAverage.C 文件里的 `calcAverages` 函数（事实上，上文提到的计算时均值的函数 `calculateMeanFieldType` 也是在这个函数中调用的）以及 fieldAverageTemplates.C 中定义的 `calculatePrime2MeanFields` 和 `addMeanSqrToPrime2Mean` 两个函数。我们看 `calcAverages` 看起\n\n```\nvoid Foam::fieldAverage::calcAverages()\n{\n   ......\n   ......\n    Info<< \"    Calculating averages\" << nl;\n\n    addMeanSqrToPrime2Mean<scalar, scalar>();\n    addMeanSqrToPrime2Mean<vector, symmTensor>();\n\n    calculateMeanFields<scalar>();\n    calculateMeanFields<vector>();\n    calculateMeanFields<sphericalTensor>();\n    calculateMeanFields<symmTensor>();\n    calculateMeanFields<tensor>();\n\n    calculatePrime2MeanFields<scalar, scalar>();\n    calculatePrime2MeanFields<vector, symmTensor>();\n  ......\n  ......\n}\n```\n可以看到，计算时均值 `mean` ，其实只要调用 `calculateMeanFields` 函数就可以了，但是计算 `prime2Mean` 则分成两部分：分别是在计算 `mean` 之前调用 `addMeanSqrToPrime2Mean` ，以及在计算 `mean` 之后调用 `calculatePrime2MeanFields`。下面一一来看这两个函数，注意到 `addMeanSqrToPrime2Mean` 和 `calculatePrime2MeanFields` 两个函数其实分别是通过调用 `addMeanSqrToPrime2MeanType` 和 `calculatePrime2MeanFieldType` 来其作用的，所以这里只看真正执行计算任务的`addMeanSqrToPrime2MeanType` 和 `calculatePrime2MeanFieldType` 函数。\n\n+ addMeanSqrToPrime2MeanType\n```\ntemplate<class Type1, class Type2>\nvoid Foam::fieldAverage::addMeanSqrToPrime2MeanType(const label fieldI) const\n{\n    const word& fieldName = faItems_[fieldI].fieldName();\n\n    if (obr_.foundObject<Type1>(fieldName))\n    {\n        const Type1& meanField =\n            obr_.lookupObject<Type1>(faItems_[fieldI].meanFieldName());\n\n        Type2& prime2MeanField = const_cast<Type2&>\n        (\n            obr_.lookupObject<Type2>(faItems_[fieldI].prime2MeanFieldName())\n        );\n\n        prime2MeanField += sqr(meanField);\n    }\n}\n```\n这个函数很简单，就是直接往上一步的 `prime2MeanField` 加上上一步的 `meanField` 。\n\n+ calculatePrime2MeanFieldType\n```\ntemplate<class Type1, class Type2>\nvoid Foam::fieldAverage::calculatePrime2MeanFieldType(const label fieldI) const\n{\n    const word& fieldName = faItems_[fieldI].fieldName();\n\n    if (obr_.foundObject<Type1>(fieldName))\n    {\n        const Type1& baseField = obr_.lookupObject<Type1>(fieldName);\n        const Type1& meanField =\n            obr_.lookupObject<Type1>(faItems_[fieldI].meanFieldName());\n\n        Type2& prime2MeanField = const_cast<Type2&>\n        (\n            obr_.lookupObject<Type2>(faItems_[fieldI].prime2MeanFieldName())\n        );\n\n        scalar dt = obr_.time().deltaTValue();\n        scalar Dt = totalTime_[fieldI];\n\n        if (faItems_[fieldI].iterBase())\n        {\n            dt = 1.0;\n            Dt = scalar(totalIter_[fieldI]);\n        }\n\n        scalar alpha = (Dt - dt)/Dt;\n        scalar beta = dt/Dt;\n\n        if (faItems_[fieldI].window() > 0)\n        {\n            const scalar w = faItems_[fieldI].window();\n\n            if (Dt - dt >= w)\n            {\n                alpha = (w - dt)/w;\n                beta = dt/w;\n            }\n        }\n\n        prime2MeanField =\n            alpha*prime2MeanField\n          + beta*sqr(baseField)\n          - sqr(meanField);\n    }\n}\n```\n这个函数里， `alpha` 与 `beta` 的定义跟上文计算时均值的函数 `calculateMeanFieldType` 是一样的，关键在于最后的那一句。\n那么，这两个函数结合起来，如何就等价于上文给出的 `prime2MeanField` 的计算公式了呢？下面做一个简单的分析：\nt = 1 时，某个场 `x` 记为 `x1` ， `meanField` 记为 `m1`，等于 `x1` ， `prime2MeanField` 记为 `pM1`，等于 0；\n\nt = 2 时，此刻的 `x` 记为 `x2` ， `meanField` 记为 `m2`，等于 `(x1+x2)/2`， `prime2MeanField` 的理论值等于 $\\frac{1}{2} [(x_1-m_2)^2+(x_2-m_2)^2]$，而按照代码，此刻的 `prime2MeanField` （记为pM2）的计算分两步， 先是计算当前步的 `meanField` 之前，执行 `prime2MeanField = pM1 + m1*m1` ，然后是在计算当前步的 `meanField` 之后，执行 `prime2MeanField = 1/2*(prime2MeanField) + 1/2*x2 - m2*m2`，结合起来，得到 `pM2 = 1/2*(pM1+m1*m1) + 1/2*x2*x2 - m2*m2 = 1/2*m1*m1 + 1/2*x2*x2 - m2*m2 =  1/2*x1*x1 + 1/2*x2*x2 - m2*m2` ， 由于 `2*m2 = x1 + x2` ，于是，上式可以进一步化简， `pM2 = 1/2*x1*x1 + 1/2*x2*x2 - 2*m2*m2 + m2*m2 = 1/2*x1*x1 + 1/2*x2*x2 -(x1+x2)*m2 + m2*m2 = 1/2*(x1*x1 -2*x1*m2 + m2*m2) + 1/2*(x2*x2 -2*x2*m2 + m2*m2) = 1/2*(x1-m2)^2 + 1/2*(x2-m2)^2` ，这就与理论值刚好对上了；\n\nt = 3时，也可以类推下去，这里简略写一下：`pM3 = 2/3*(pM2 + m2*m2) + 1/3*x3*x3 - m3*m3 = 1/3*(x1*x1 + x2*x2 + x3*x3) - m3*m3 = 1/3*(x1*x1 + x2*x2 + x3*x3) - 2/3*(x1+x2+x3)*m3 + m3*m3 = 1/3*[(x1-m3)^2 + (x2-m3)^2 + (x3-m3)^2]`，同样是跟理论值是一样的。 \n\n","source":"_posts/fieldAverage.md","raw":"title: \"fieldAverage 使用说明\"\ndate: 2015-04-12 17:00:54\ncomments: true\ntags:\n  - OpenFOAM\n  - Postprocessing\ncategories: \n  - OpenFOAM\n---\n\n`fieldAverage`是 OpenFOAM 中的一种 functionObject，用来计算时均值。其基本用法是作为一个 function object 放在 controlDict 文件中，运行 solver 的同时计算指定场的时均值，以下是一个示例：\n\n```\nfunctions\n{\n    fieldAverage1\n    {\n        type            fieldAverage;\n        functionObjectLibs ( \"libfieldFunctionObjects.so\" );\n        outputControl   outputTime;\n        fields\n        (\n            Ua\n            {\n                 mean        on;\n                 prime2Mean  off;\n                 base        time;\n            }\n            Ub\n            {\n                 mean        on;\n                 prime2Mean  off;\n                 base        time;\n            }\n        );\n    }\n}\n```\n<!--more-->\n\n但是，在有效地使用`fieldAverage`之前，有一些问题需要澄清，其中最重要的一个是：**`fieldAverage`到底在对哪个时间段求的时间平均**？本文通过对`fieldAverage`的源码进行分析，试图厘清这些细节，并给出一个可靠的`fieldAverage`使用说明。涉及到的源文件包括：`fieldAverage.H`, `fieldAverage.C`, `fieldAverageTemplates.C`, `fieldAverageItem.C`，位于目录`$WM_PROJECT_DIR/src/postProcessing/functionObjects/field/fieldAverage`下。\n#####首要问题：**`fieldAverage`对哪个时间区间进行时均计算**？\n上面这段代码是从我运行的一个算例的 controlDict 中摘出来的。算例运行过程中发现，每一个时间步都要调用计算时均值相关的代码，并屏幕上的输出也会打印出`Calculating averages`，而每一个输出数据文件里，都有`*Mean`数据，其中`*`表示在`controlDict`中定义了需要求解时均值的某个场。\n经过摸索发现，跟时均值求解密切相关的一段代码位于[`fieldAverageTemplates.C`](http://foam.sourceforge.net/docs/cpp/a08780_source.html)，下面是从中摘出来的一段核心代码：\n```c++\n182 template<class Type>\n183 void Foam::fieldAverage::calculateMeanFieldType(const label fieldI) const\n184 {\n185  const word& fieldName = faItems_[fieldI].fieldName();\n187  if (obr_.foundObject<Type>(fieldName))\n188  {\n189    const Type& baseField = obr_.lookupObject<Type>(fieldName);\n190 \n191    Type& meanField = const_cast<Type&>\n192    (\n193    obr_.lookupObject<Type>(faItems_[fieldI].meanFieldName())\n194    );\n195 \n196    scalar dt = obr_.time().deltaTValue();\n197    scalar Dt = totalTime_[fieldI];\n198 \n199    if (faItems_[fieldI].iterBase())\n200    {\n201      dt = 1.0;\n202      Dt = scalar(totalIter_[fieldI]);\n203    }\n204 \n205    scalar alpha = (Dt - dt)/Dt;\n206    scalar beta = dt/Dt;\n207 \n208    if (faItems_[fieldI].window() > 0)\n209    {\n210      const scalar w = faItems_[fieldI].window();\n211 \n212      if (Dt - dt >= w)\n213      {\n214        alpha = (w - dt)/w;\n215        beta = dt/w;\n216      }\n217    }\n219    meanField = alpha*meanField + beta*baseField;\n220  }\n221 }\n\n```\n以下是我根据理解整理的代码解析：\n  1. 变量`baseField`定义为当前时间步计算得到的场的值(Ua)，`meanField`定义为上一个时间步对应的场的时均值(UaMean)。\n  2. `dt`定义为时间步长，`Dt`定义为当前所在的时间。注意199行，`iterBase()`这个函数定义为当`base` (见controlDict) 为`ITER`时，返回`true`。此时，`dt`定义为1，意义很显然，当以时间步数为基准的时候，下一步和上一步当然是只相差1个时间步；`Dt`则被定义为当前所在的时间步(第xx步)。所以，`Dt`和`dt`的真实含义取决于`base`。**`fieldAverageItem.C`中给`base`赋了默认值为`ITER`**。`base`可以指定为`time`，具体见下文。\n  3. alpha 定义为(Dt-dt)/Dt, beta 定义为 dt/Dt。然后当前时间步的`meanField`定义为`alpha*meanField + beta*baseField`。举例说明，假设dt = 1, Dt = 20, 假设上一时间步的时均值为`meanField_pre`，则新时刻时均值为\n   ```\n    19/20*meanField_pre + 1/20*baseField) = (19*meanField_pre + baseField)/20\n   ```\n  `19*meanField_pre`的含义很明显，前19步的时均值乘以总时间步数，也就是前 19 步 Field 值的加和，再加上当前时间步的值`baseField`，就是前 20 步的 Field 值的和。用这个值除以20，得到新时刻的时均值`meanField`。由此可见，在这种情况下，**每个时间步输出的时均值是从开始时刻到当前时间步的Field值对总时间的平均**。\n  4. 很多时候，我们并不想得到从开始到结束整个模拟时段内的平均值，而希望得到指定时段内的平均值，这时，可以通过指定`window`变量的值来达到目的。代码208行，注意到这里出现了一个新函数`window`。可以查到`window`函数返回值默认是`-1`。但可以在controlDict中`base`下面定义变量`windows`的值，当`window`函数返回值大于0时，208-217将被执行。接着上一点的假设，dt = 1, 定义并初始化为`window`函数的返回值，假设为20。则： \n    + 当 `Dt` 小于或等于20时，不满足`Dt - dt >= w`，此时时均值将按照上一点叙述的进行计算。\n    + 当 `Dt`大于20以后，假设`Dt = 21`，此时 `alpha = (w-dt)/w = (20-1)/20`，`bata = dt/w = 1/19`。令`meanField_pre`为`Dt = 20` 时的时均值，则`Dt = 21`时的时均值\n    ```\n    meanField = 19/20 * meanField_pre + 1/20 * baseField = (19 * meanField_pre + baseField)/20\n    ```\n    对比发现，如果`window = -1`(默认值)，则`Dt = 21`的时均值应该是`20/21 * meanField_pre + 1/21 * baseField`。而定义了`windows = 20`以后，此处的`19 * meanField_pre` 可以理解为`Dt = 21`这一步之前的19步的Field值的和，加上`baseField`，则为`Dt = 21` 以及其之前19步，共20步的Field值的和。再除以20，则为 `Dt = 21 `以及其之前19步这20步的时均值。**所以，当设定`window`为一正整数`w`时，输出的时均值是当前时间步以及其之前`w-1`步，这`w`步内Field的时均值**。\n     \n附上一个更一般化的示例：\n```\nfieldAverage1\n{\n    type fieldAverage;\n    functionObjectLibs (\"libfieldFunctionObjects.so\");\n    resetOnRestart true;\n    resetOnOutput false;\n    startTime     0.1; // 开始计算时均值的时间\n    endTime       1.0; // 停止计算时均值的时间\n    outputControl   outputTime;\n    fields\n    (\n        U\n        {\n            mean            on;\n            prime2Mean      on;\n            base            time; //以物理时间为基础来计算平均，而不是时间步数。\n            window          10.0;\n            windowName      w1; //optional\n        }\n        p\n        {\n            mean            on;\n            prime2Mean      on;\n            base            time;\n        }\n    );\n}\n\n```\n`resetOnRestart`的值决定当solver继续运行时，是否要读取最近一个时间步的`meanField`的值来计算接下来时刻的时均值；`resetOnOutput`，顾名思义，是否要在每一次输出到文件以后重置`meanField`的值。这两个开关的默认值都是`false`。\n`mean`这个开关的含义无需多言，计算公式如下：\n$$ \\overline {x} = \\frac{1}{N} \\sum \\limits \\_{i=0}^{N} x\\_i $$\n`prime2Mean`的计算公式如下：\n$$ \\overline{x'}^{\\, 2} = \\frac{1}{N}\\displaystyle\\sum\\limits\\_{i=0}^N (x\\_i - \\overline{x})^2 $$\n所以，如果`prime2Mean`为`on`，`mean`必须为`on`。\n\n\n此外，如果计算已经结束，controlDict 中定义的 function 仍可以用`execFlowFunctionObjects`来执行。只是，这样的运行只能利用输出到文件的数据来进行计算了。举例说，假如时间步长是 0.001s， 每 0.1s 输出一次，那么同样是1-2s的时均值，solver运行过程中求解的公式是:\n```\n( field.at(1.001) +field.at(1.002) + ... + field.at(2.0) )/1000\n```\n而solver运行完以后利用`execFlowFunctionObjects`计算的时均值应该是：\n```\n( field.at(1.1) + field.at(1.2) + ... + field.at(2) )/10\n```\n注意，这个结果没有经过直接的验证，是我根据原理推演的结果。\n有时候，运行`execFlowFunctionObjects`会报错说找不到`phi`，这时加上`-noFlow`选项，就不会报错了。\n\n##### 小结\n1. `base`用来指定作时间平均的基础，是基于时间步数(ITER)还是物理时间(time);\n2. `window`用来作平均的时间段的长度，如果不设定，则求的是从开始到当前时间这个时间段的平均值。`window`的数值的实际含义依`base`而定，如果`base`是`ITER`，则`window=20`表示当前步及其前 19 个时间步从 20 个时间步内的平均，而如果`base`是`time`,则表示的是 20s 内的平均。\n\n**错误更正**：\n2015.09.05: 在 controlDict 里， `base` 只能是  `time` 或 `iteration`，而不能是上文中说的 `ITER`， `ITER` 只是代码中使用的一个代称 。感谢“启之” 指出！  \n\n**补充**:\n2015.11.26：上文没有提 `prime2Mean` 的计算方法，今天仔细看了一下，记录如下。\n `prime2Mean` 的公式上面提到了，计算代码需要结合三个个函数来看，分别是 fieldAverage.C 文件里的 `calcAverages` 函数（事实上，上文提到的计算时均值的函数 `calculateMeanFieldType` 也是在这个函数中调用的）以及 fieldAverageTemplates.C 中定义的 `calculatePrime2MeanFields` 和 `addMeanSqrToPrime2Mean` 两个函数。我们看 `calcAverages` 看起\n\n```\nvoid Foam::fieldAverage::calcAverages()\n{\n   ......\n   ......\n    Info<< \"    Calculating averages\" << nl;\n\n    addMeanSqrToPrime2Mean<scalar, scalar>();\n    addMeanSqrToPrime2Mean<vector, symmTensor>();\n\n    calculateMeanFields<scalar>();\n    calculateMeanFields<vector>();\n    calculateMeanFields<sphericalTensor>();\n    calculateMeanFields<symmTensor>();\n    calculateMeanFields<tensor>();\n\n    calculatePrime2MeanFields<scalar, scalar>();\n    calculatePrime2MeanFields<vector, symmTensor>();\n  ......\n  ......\n}\n```\n可以看到，计算时均值 `mean` ，其实只要调用 `calculateMeanFields` 函数就可以了，但是计算 `prime2Mean` 则分成两部分：分别是在计算 `mean` 之前调用 `addMeanSqrToPrime2Mean` ，以及在计算 `mean` 之后调用 `calculatePrime2MeanFields`。下面一一来看这两个函数，注意到 `addMeanSqrToPrime2Mean` 和 `calculatePrime2MeanFields` 两个函数其实分别是通过调用 `addMeanSqrToPrime2MeanType` 和 `calculatePrime2MeanFieldType` 来其作用的，所以这里只看真正执行计算任务的`addMeanSqrToPrime2MeanType` 和 `calculatePrime2MeanFieldType` 函数。\n\n+ addMeanSqrToPrime2MeanType\n```\ntemplate<class Type1, class Type2>\nvoid Foam::fieldAverage::addMeanSqrToPrime2MeanType(const label fieldI) const\n{\n    const word& fieldName = faItems_[fieldI].fieldName();\n\n    if (obr_.foundObject<Type1>(fieldName))\n    {\n        const Type1& meanField =\n            obr_.lookupObject<Type1>(faItems_[fieldI].meanFieldName());\n\n        Type2& prime2MeanField = const_cast<Type2&>\n        (\n            obr_.lookupObject<Type2>(faItems_[fieldI].prime2MeanFieldName())\n        );\n\n        prime2MeanField += sqr(meanField);\n    }\n}\n```\n这个函数很简单，就是直接往上一步的 `prime2MeanField` 加上上一步的 `meanField` 。\n\n+ calculatePrime2MeanFieldType\n```\ntemplate<class Type1, class Type2>\nvoid Foam::fieldAverage::calculatePrime2MeanFieldType(const label fieldI) const\n{\n    const word& fieldName = faItems_[fieldI].fieldName();\n\n    if (obr_.foundObject<Type1>(fieldName))\n    {\n        const Type1& baseField = obr_.lookupObject<Type1>(fieldName);\n        const Type1& meanField =\n            obr_.lookupObject<Type1>(faItems_[fieldI].meanFieldName());\n\n        Type2& prime2MeanField = const_cast<Type2&>\n        (\n            obr_.lookupObject<Type2>(faItems_[fieldI].prime2MeanFieldName())\n        );\n\n        scalar dt = obr_.time().deltaTValue();\n        scalar Dt = totalTime_[fieldI];\n\n        if (faItems_[fieldI].iterBase())\n        {\n            dt = 1.0;\n            Dt = scalar(totalIter_[fieldI]);\n        }\n\n        scalar alpha = (Dt - dt)/Dt;\n        scalar beta = dt/Dt;\n\n        if (faItems_[fieldI].window() > 0)\n        {\n            const scalar w = faItems_[fieldI].window();\n\n            if (Dt - dt >= w)\n            {\n                alpha = (w - dt)/w;\n                beta = dt/w;\n            }\n        }\n\n        prime2MeanField =\n            alpha*prime2MeanField\n          + beta*sqr(baseField)\n          - sqr(meanField);\n    }\n}\n```\n这个函数里， `alpha` 与 `beta` 的定义跟上文计算时均值的函数 `calculateMeanFieldType` 是一样的，关键在于最后的那一句。\n那么，这两个函数结合起来，如何就等价于上文给出的 `prime2MeanField` 的计算公式了呢？下面做一个简单的分析：\nt = 1 时，某个场 `x` 记为 `x1` ， `meanField` 记为 `m1`，等于 `x1` ， `prime2MeanField` 记为 `pM1`，等于 0；\n\nt = 2 时，此刻的 `x` 记为 `x2` ， `meanField` 记为 `m2`，等于 `(x1+x2)/2`， `prime2MeanField` 的理论值等于 $\\frac{1}{2} [(x_1-m_2)^2+(x_2-m_2)^2]$，而按照代码，此刻的 `prime2MeanField` （记为pM2）的计算分两步， 先是计算当前步的 `meanField` 之前，执行 `prime2MeanField = pM1 + m1*m1` ，然后是在计算当前步的 `meanField` 之后，执行 `prime2MeanField = 1/2*(prime2MeanField) + 1/2*x2 - m2*m2`，结合起来，得到 `pM2 = 1/2*(pM1+m1*m1) + 1/2*x2*x2 - m2*m2 = 1/2*m1*m1 + 1/2*x2*x2 - m2*m2 =  1/2*x1*x1 + 1/2*x2*x2 - m2*m2` ， 由于 `2*m2 = x1 + x2` ，于是，上式可以进一步化简， `pM2 = 1/2*x1*x1 + 1/2*x2*x2 - 2*m2*m2 + m2*m2 = 1/2*x1*x1 + 1/2*x2*x2 -(x1+x2)*m2 + m2*m2 = 1/2*(x1*x1 -2*x1*m2 + m2*m2) + 1/2*(x2*x2 -2*x2*m2 + m2*m2) = 1/2*(x1-m2)^2 + 1/2*(x2-m2)^2` ，这就与理论值刚好对上了；\n\nt = 3时，也可以类推下去，这里简略写一下：`pM3 = 2/3*(pM2 + m2*m2) + 1/3*x3*x3 - m3*m3 = 1/3*(x1*x1 + x2*x2 + x3*x3) - m3*m3 = 1/3*(x1*x1 + x2*x2 + x3*x3) - 2/3*(x1+x2+x3)*m3 + m3*m3 = 1/3*[(x1-m3)^2 + (x2-m3)^2 + (x3-m3)^2]`，同样是跟理论值是一样的。 \n\n","slug":"fieldAverage","published":1,"updated":"2015-12-11T12:39:01.512Z","layout":"post","photos":[],"link":"","_id":"cioiqegdp002zz8mbqf4wbghq"},{"title":"OpenFOAM 不可压缩湍流模型的 divDevReff 函数","date":"2016-05-03T05:39:44.000Z","comments":1,"_content":"\n3.0 版本之前，OpenFOAM 的单相流求解器如 pisoFoam 的动量方程中调用的是湍流模型的 `divDevReff` 函数来考虑雷诺应力项的作用。只是，细究起来，这个函数似乎有点小问题，本篇来探讨一下这些小问题。\n\n<!--more-->\n\n\nOpenFOAM 中单相不可压缩求解器中，雷诺应力项调用的是湍流模型中的 `divDevReff` 函数。这个函数的返回值为\n$$\n\\nabla \\cdot(\\nu\\_{eff}\\nabla U)+\\nabla \\cdot\\left [\\nu\\_{eff}\\nabla U^\\mathrm{T}-\\frac{1}{3} \\nu\\_{eff} (\\nabla \\cdot U) \\mathbf{I} \\right ]\n$$\n这里有两个疑问，第一是为什么是 $\\frac{1}{3}$ 而不是 $\\frac{2}{3}$，对应到代码，即为什么用 `dev` 函数而不是 `dev2` 函数？第二个问题，根据涡粘度的 Boussinesq approximation，雷诺应力项\n$$\n\\overline{u'\\_iu'\\_j}= -\\nu\\_t \\left( \\frac{\\partial \\bar{u}\\_i}{\\partial x\\_j} + \\frac{\\partial \\bar{u}\\_j}{\\partial x\\_i} \\right) + \\frac{2}{3}k \\delta\\_{ij}\n$$\n中应该还包含湍动能 $k$，而 OpenFOAM 中的 `divDefReff` 函数是没有 $k$ 这一项的。\n这个话题，在 cfd-online 上的[一篇帖子](http://www.cfd-online.com/Forums/openfoam-solving/58214-calculating-divdevreff-2.html)里有深入的讨论。\n\n对于第一个问题，我跟[Holtzmann CFD](http://www.holzmann-cfd.de/index.php/en/tutorials-en) 博客的博主 Tobias Holzmann 持同样观点，即$\\frac{1}{3}$ 或 $\\frac{2}{3}$ 不重要，因为对于不可压缩流动，连续方程为\n$$\n\\nabla \\cdot U = 0\n$$\n所以，收敛以后，$\\frac{1}{3} \\nu\\_{eff} (\\nabla \\cdot U)$ 这一项等于0. 但是在开始阶段，或者说还没有达到满足连续性的流场之前，这一项不为零。这里加上这一项是出于数值稳定性以及收敛速度的考虑，这一项不对收敛后的结果几乎没有影响。所以，$\\frac{1}{3}$ 或 $\\frac{2}{3}$ 不是很重要。\n但是，可压缩湍流模型里必须是 $\\frac{2}{3}$ ，因为这个 $\\frac{2}{3}$ 是从 N-S 方程中严格推导而来的，而且，在可压缩的情形下，即使收敛以后，也有 $\\nabla \\cdot U \\neq 0$ 。在 OpenFOAM=3.0 以后的版本里，不可压和可压缩湍流模型纳入到一个框架下了，两种情形下，都是用的 $\\frac{2}{3}$ 这个系数。\n\n对于第二个问题，有两种观点，一种认为 $k$ 的值相对很小，可以直接忽略不计。另一种观点认为，$k$ 被放到了压力项里，即，动量方程中的压力是雷诺时均压力与雷诺应力的各向同性分量（即 $\\frac{2}{3}k$）之和：\n$$\n\\bar{u}\\_j\\frac{\\partial\\bar{u}\\_i}{\\partial\\bar{u}\\_j} =\n-\\frac{1}{\\rho}\\frac{\\partial}{\\partial x\\_i} \\underbrace{\\left[p + \\frac{2}{3}k \\right]}\\_{p^\\prime}\n\\+ \\frac{\\partial}{\\partial x\\_j} \\left[(\\nu+\\nu\\_{t}) \\left( \\frac{\\partial \\bar{u}\\_i}{\\partial x\\_j} + \\frac{\\partial \\bar{u}\\_j}{\\partial x\\_i}\\right) \\right]\n$$\n这种观点可以在 Pope 2000 书第 88 页找到依据。在 \" The Finite Volume Method in Computational Fluid Dynamics: An Advanced Introduction with OpenFOAM® and Matlab®\" 这本书的第 699 页，也提到 $k$ 是被放到压力项里去了，目的在于使动量方程中只含有 $\\nu\\_t$ 这一个跟湍流有关的未知量。\n \n不过，Tobias Holzmann 最后仍持前一种观点，即 $k$ 项被忽略了。理由是 OpenFOAM 中似乎找不到关于修改的压力场的代码，而且 OpenFOAM 那边也没见有人讨论说 OpenFOAM 中使用的是修改的压力场。\n第二个问题目前还没有确切的结论，也尚不清楚这样处理对结果有多大影响。\n","source":"_posts/divDevReff.md","raw":"title: \"OpenFOAM 不可压缩湍流模型的 divDevReff 函数\"\ndate: 2016-05-03 13:39:44\ncomments: true\ntags:\n- turbulence model\ncategories:\n- OpenFOAM\n---\n\n3.0 版本之前，OpenFOAM 的单相流求解器如 pisoFoam 的动量方程中调用的是湍流模型的 `divDevReff` 函数来考虑雷诺应力项的作用。只是，细究起来，这个函数似乎有点小问题，本篇来探讨一下这些小问题。\n\n<!--more-->\n\n\nOpenFOAM 中单相不可压缩求解器中，雷诺应力项调用的是湍流模型中的 `divDevReff` 函数。这个函数的返回值为\n$$\n\\nabla \\cdot(\\nu\\_{eff}\\nabla U)+\\nabla \\cdot\\left [\\nu\\_{eff}\\nabla U^\\mathrm{T}-\\frac{1}{3} \\nu\\_{eff} (\\nabla \\cdot U) \\mathbf{I} \\right ]\n$$\n这里有两个疑问，第一是为什么是 $\\frac{1}{3}$ 而不是 $\\frac{2}{3}$，对应到代码，即为什么用 `dev` 函数而不是 `dev2` 函数？第二个问题，根据涡粘度的 Boussinesq approximation，雷诺应力项\n$$\n\\overline{u'\\_iu'\\_j}= -\\nu\\_t \\left( \\frac{\\partial \\bar{u}\\_i}{\\partial x\\_j} + \\frac{\\partial \\bar{u}\\_j}{\\partial x\\_i} \\right) + \\frac{2}{3}k \\delta\\_{ij}\n$$\n中应该还包含湍动能 $k$，而 OpenFOAM 中的 `divDefReff` 函数是没有 $k$ 这一项的。\n这个话题，在 cfd-online 上的[一篇帖子](http://www.cfd-online.com/Forums/openfoam-solving/58214-calculating-divdevreff-2.html)里有深入的讨论。\n\n对于第一个问题，我跟[Holtzmann CFD](http://www.holzmann-cfd.de/index.php/en/tutorials-en) 博客的博主 Tobias Holzmann 持同样观点，即$\\frac{1}{3}$ 或 $\\frac{2}{3}$ 不重要，因为对于不可压缩流动，连续方程为\n$$\n\\nabla \\cdot U = 0\n$$\n所以，收敛以后，$\\frac{1}{3} \\nu\\_{eff} (\\nabla \\cdot U)$ 这一项等于0. 但是在开始阶段，或者说还没有达到满足连续性的流场之前，这一项不为零。这里加上这一项是出于数值稳定性以及收敛速度的考虑，这一项不对收敛后的结果几乎没有影响。所以，$\\frac{1}{3}$ 或 $\\frac{2}{3}$ 不是很重要。\n但是，可压缩湍流模型里必须是 $\\frac{2}{3}$ ，因为这个 $\\frac{2}{3}$ 是从 N-S 方程中严格推导而来的，而且，在可压缩的情形下，即使收敛以后，也有 $\\nabla \\cdot U \\neq 0$ 。在 OpenFOAM=3.0 以后的版本里，不可压和可压缩湍流模型纳入到一个框架下了，两种情形下，都是用的 $\\frac{2}{3}$ 这个系数。\n\n对于第二个问题，有两种观点，一种认为 $k$ 的值相对很小，可以直接忽略不计。另一种观点认为，$k$ 被放到了压力项里，即，动量方程中的压力是雷诺时均压力与雷诺应力的各向同性分量（即 $\\frac{2}{3}k$）之和：\n$$\n\\bar{u}\\_j\\frac{\\partial\\bar{u}\\_i}{\\partial\\bar{u}\\_j} =\n-\\frac{1}{\\rho}\\frac{\\partial}{\\partial x\\_i} \\underbrace{\\left[p + \\frac{2}{3}k \\right]}\\_{p^\\prime}\n\\+ \\frac{\\partial}{\\partial x\\_j} \\left[(\\nu+\\nu\\_{t}) \\left( \\frac{\\partial \\bar{u}\\_i}{\\partial x\\_j} + \\frac{\\partial \\bar{u}\\_j}{\\partial x\\_i}\\right) \\right]\n$$\n这种观点可以在 Pope 2000 书第 88 页找到依据。在 \" The Finite Volume Method in Computational Fluid Dynamics: An Advanced Introduction with OpenFOAM® and Matlab®\" 这本书的第 699 页，也提到 $k$ 是被放到压力项里去了，目的在于使动量方程中只含有 $\\nu\\_t$ 这一个跟湍流有关的未知量。\n \n不过，Tobias Holzmann 最后仍持前一种观点，即 $k$ 项被忽略了。理由是 OpenFOAM 中似乎找不到关于修改的压力场的代码，而且 OpenFOAM 那边也没见有人讨论说 OpenFOAM 中使用的是修改的压力场。\n第二个问题目前还没有确切的结论，也尚不清楚这样处理对结果有多大影响。\n","slug":"divDevReff","published":1,"updated":"2016-05-03T05:58:35.645Z","layout":"post","photos":[],"link":"","_id":"cioiqegdt0033z8mby34fv2qf"},{"title":"blockMesh 的新功能：multi/sectional grading in a block","date":"2015-10-06T12:24:52.000Z","comments":1,"_content":"\n从 OpenFOAM-2.3.x 的 \"[commit cf370883644ec59782be375041b2434eb3e2c4ed](https://github.com/OpenFOAM/OpenFOAM-2.3.x/commit/cf370883644ec59782be375041b2434eb3e2c4ed)\" 开始，`blockMesh` 有了一项新功能：multi/sectional grading in a block。这项功能说起来很简单即可以在同一个block里面设置多个方向的大小渐变网格。举个例子，假设你想画一个简单的二维槽道流网格，你希望两边靠近壁面处的网格更密一点，而中心的网格稀疏一点。以前版本的 `blockMesh` 由于只支持让网格大小在某一个方向上渐变（通过设置 simpleGrading），要实现上述网格，你需要将整个槽道分成两个block，然后分别设置 simpleGrading。现在有了 \"multi/sectional grading\"以后，只需要一个block，并设置好simpleGrading便可实现了。下面解释一下新版 `blockMesh` 具体设置，并以一个二维方腔流例子来说明。\n\n<!--more-->\n\n先来解释一下参数的含义，下面是我设置的一个二维方腔流算例的 blockMeshDict 文件的部分：\n```\nvertices\n(\n    (0 0 0)\n    (1 0 0)\n    (1 1 0)\n    (0 1 0)\n    (0 0 0.1)\n    (1 0 0.1)\n    (1 1 0.1)\n    (0 1 0.1)\n);\n\nblocks\n(\n    hex (0 1 2 3 4 5 6 7) (100 100 1)\n    simpleGrading\n    (\n      ((0.5 0.5 2)  (0.5 0.5 0.5))\n      ((0.5 0.5 2)  (0.5 0.5 0.5))\n      1\n    )\n);\n```\n跟前面版本的差别在于 simpleGrading 部分的参数更多了。下面解释一下多出来的参数的含义。首先，注意 simpleGrading 的参数仍然是分成三段，分别代表x，y，z方向的网格渐变设置。其中，x方向的渐变设置参数又分作两段：\"(0.5 0.5 2)\"和\"(0.5 0.5 0.5)\"，每一段均代表一个子 block，这个子 block 的网格参数由三个数字来确定。以\"(0.5 0.5 2)\" 为例，第一个\"0.5\" 表示该子 block 的尺度是总 block 的尺度的0.5倍；第二个\"0.5\"表示在这个子block里划分的网格总数占整个block的网格总数的一半；\"2\" 代表着网格渐变因子，这个跟以前的 blockMesh 的 simpleGrading 参数的含义一样。\n\n了解了参数的含义，便可以推知上述 blockMeshDict 文件对应的网格了：二维方腔，x和y方向上各分为两个子 block，子 block 的尺度都是整个 block 的一半，且子 block 的网格渐变因子分别为 \"2\" 和 \"0.5\"。得到的网格如下：\n\n![整体](/image/blockMesh/mesh_whole.png)\n\n右上角局部放大图如下：\n![右上角局部放大](/image/blockMesh/mesh_local.png)\n\n上述描述中有一个不是很严谨的地方，用下面这个例子来说明一下：\n```\nblocks\n(\n    hex (0 1 2 3 4 5 6 7) (20 60 20)\n    simpleGrading\n    (\n        1\n        ((2 3 4) (6 4 1) (2 3 0.25))\n        1\n    )\n);\n```\n按照上文的描述，这个网格将在y方向上划分成3个子 block，其中第一个子 block 的尺度是整个 block 的2倍...等等，2倍？这怎么可能？实际情况是，blockMesh 会自动对参数进行归一化，第一个子 block 的尺度将是整个 block 的 2/(2+6+2)=0.2 倍，其余的依此类推。\n","source":"_posts/blockMesh-multi-sectional-grading.md","raw":"title: \"blockMesh 的新功能：multi/sectional grading in a block\"\ndate: 2015-10-06 20:24:52\ncomments: true\ntags:\n - OpenFOAM\n - Preprocessing\ncategories:\n - OpenFOAM\n---\n\n从 OpenFOAM-2.3.x 的 \"[commit cf370883644ec59782be375041b2434eb3e2c4ed](https://github.com/OpenFOAM/OpenFOAM-2.3.x/commit/cf370883644ec59782be375041b2434eb3e2c4ed)\" 开始，`blockMesh` 有了一项新功能：multi/sectional grading in a block。这项功能说起来很简单即可以在同一个block里面设置多个方向的大小渐变网格。举个例子，假设你想画一个简单的二维槽道流网格，你希望两边靠近壁面处的网格更密一点，而中心的网格稀疏一点。以前版本的 `blockMesh` 由于只支持让网格大小在某一个方向上渐变（通过设置 simpleGrading），要实现上述网格，你需要将整个槽道分成两个block，然后分别设置 simpleGrading。现在有了 \"multi/sectional grading\"以后，只需要一个block，并设置好simpleGrading便可实现了。下面解释一下新版 `blockMesh` 具体设置，并以一个二维方腔流例子来说明。\n\n<!--more-->\n\n先来解释一下参数的含义，下面是我设置的一个二维方腔流算例的 blockMeshDict 文件的部分：\n```\nvertices\n(\n    (0 0 0)\n    (1 0 0)\n    (1 1 0)\n    (0 1 0)\n    (0 0 0.1)\n    (1 0 0.1)\n    (1 1 0.1)\n    (0 1 0.1)\n);\n\nblocks\n(\n    hex (0 1 2 3 4 5 6 7) (100 100 1)\n    simpleGrading\n    (\n      ((0.5 0.5 2)  (0.5 0.5 0.5))\n      ((0.5 0.5 2)  (0.5 0.5 0.5))\n      1\n    )\n);\n```\n跟前面版本的差别在于 simpleGrading 部分的参数更多了。下面解释一下多出来的参数的含义。首先，注意 simpleGrading 的参数仍然是分成三段，分别代表x，y，z方向的网格渐变设置。其中，x方向的渐变设置参数又分作两段：\"(0.5 0.5 2)\"和\"(0.5 0.5 0.5)\"，每一段均代表一个子 block，这个子 block 的网格参数由三个数字来确定。以\"(0.5 0.5 2)\" 为例，第一个\"0.5\" 表示该子 block 的尺度是总 block 的尺度的0.5倍；第二个\"0.5\"表示在这个子block里划分的网格总数占整个block的网格总数的一半；\"2\" 代表着网格渐变因子，这个跟以前的 blockMesh 的 simpleGrading 参数的含义一样。\n\n了解了参数的含义，便可以推知上述 blockMeshDict 文件对应的网格了：二维方腔，x和y方向上各分为两个子 block，子 block 的尺度都是整个 block 的一半，且子 block 的网格渐变因子分别为 \"2\" 和 \"0.5\"。得到的网格如下：\n\n![整体](/image/blockMesh/mesh_whole.png)\n\n右上角局部放大图如下：\n![右上角局部放大](/image/blockMesh/mesh_local.png)\n\n上述描述中有一个不是很严谨的地方，用下面这个例子来说明一下：\n```\nblocks\n(\n    hex (0 1 2 3 4 5 6 7) (20 60 20)\n    simpleGrading\n    (\n        1\n        ((2 3 4) (6 4 1) (2 3 0.25))\n        1\n    )\n);\n```\n按照上文的描述，这个网格将在y方向上划分成3个子 block，其中第一个子 block 的尺度是整个 block 的2倍...等等，2倍？这怎么可能？实际情况是，blockMesh 会自动对参数进行归一化，第一个子 block 的尺度将是整个 block 的 2/(2+6+2)=0.2 倍，其余的依此类推。\n","slug":"blockMesh-multi-sectional-grading","published":1,"updated":"2015-11-03T13:38:21.454Z","layout":"post","photos":[],"link":"","_id":"cioiqegdw0037z8mblc4pbpjd"},{"title":"OpenFOAM-3.0 的湍流模型（一）","date":"2016-04-24T13:35:17.000Z","comments":1,"_content":"\n本系列分析 OpenFOAM-3.0 版本的湍流模型。从 3.0 版开始，OpenFOAM 中的湍流模型架构发生了较大的变化，其实这种变化在 2.3 版开始已经初露端倪，在 2.3 版里，多相流的湍流模型已经开始跟单相流湍流模型分开。从 3.0 开始，单相流湍流模型和多相流湍流模型统一到了一个架构下。本系列将对 3.0 版的湍流模型进行详细的分析，分为四部分：结构概览，RTS 机制分析，编译新模型的方法，以及一些补充说明。\n\n<!--more-->\n\n#####　1.  结构概览\n\n这部分主要是概括一下湍流模型的框架的结构，如下图（请点击右键查看大图）：\n\n![湍流模型的架构](/image/turbulenceModel/turbulenceModel-3.0.png)\n\n图片中，蓝色字体的是类名，绿框中的类是调用 `declareRunTimeSeclectionTable` 的类（如果对这个的含义感兴趣，建议参考[这篇](http://xiaopingqiu.github.io/2016/03/12/RTS1/)或者[这篇](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/)），四种不同颜色的箭头，代表的是四种不同的湍流模型：单相不可压缩湍流模型，单相可压缩湍流模型，多相不可压缩模型，多相可压缩模型。\n\n在图片下面，我用了一个 `kEpsilon` 和一个 `Smagorinsky` 模型作为示例，这是因为，这两个湍流模型都是以通用形式来实现的，从 C++ 角度来说，就是模板类。通过代入不同的模板参数， `kEpsilon` 和 `Smagorinsky` 这两个模板类可以实例化为不同种类的湍流模型。\n```\ntemplate<class BasicTurbulenceModel>\nclass kEpsilon\n:\n    public eddyViscosity<RASModel<BasicTurbulenceModel> >\n{\n    // Private Member Functions\n\n        // Disallow default bitwise copy construct and assignment\n        kEpsilon(const kEpsilon&);\n        kEpsilon& operator=(const kEpsilon&);\n\n\t......\n\t......\n\n tmp<fvScalarMatrix> epsEqn\n    (\n        fvm::ddt(alpha, rho, epsilon_)\n      + fvm::div(alphaRhoPhi, epsilon_)\n      - fvm::laplacian(alpha*rho*DepsilonEff(), epsilon_)\n     ==\n        C1_*alpha*rho*G*epsilon_/k_\n      - fvm::SuSp(((2.0/3.0)*C1_ + C3_)*alpha*rho*divU, epsilon_)\n      - fvm::Sp(C2_*alpha*rho*epsilon_/k_, epsilon_)\n      + epsilonSource()\n      + fvOptions(alpha, rho, epsilon_)\n    );\n\n    epsEqn().relax();\n    fvOptions.constrain(epsEqn());\n    epsEqn().boundaryManipulate(epsilon_.boundaryField());\n    solve(epsEqn);\n    fvOptions.correct(epsilon_);\n    bound(epsilon_, this->epsilonMin_);\n\n    // Turbulent kinetic energy equation\n    tmp<fvScalarMatrix> kEqn\n    (\n        fvm::ddt(alpha, rho, k_)\n      + fvm::div(alphaRhoPhi, k_)\n      - fvm::laplacian(alpha*rho*DkEff(), k_)\n     ==\n        alpha*rho*G\n      - fvm::SuSp((2.0/3.0)*alpha*rho*divU, k_)\n      - fvm::Sp(alpha*rho*epsilon_/k_, k_)\n      + kSource()\n      + fvOptions(alpha, rho, k_)\n    );\n\t\n```\n从上述代码可以看出，输运方程中带入了密度 `rho` 和代表相体积分率的 `alpha` 。代入不同的模板参数， `rho` 和 `alpha` 的取值也会不同，从而实例化为不同的湍流模型，详细的后文还会分析。除此之外，输运方程中还加入了两种源项的实现，一种是以成员函数的形式（ `epsilonSource()` 和 `kEpsilon()` ）；另一种是以 `fvOptions` 的形式，允许用户自定义源项。\n\n在这个架构下，湍流模型是怎么通过 RTS 机制来进行选择的呢？请看下一篇。\n","source":"_posts/TurbulenceModel-30-structure.md","raw":"title: \"OpenFOAM-3.0 的湍流模型（一）\"\ndate: 2016-04-24 21:35:17\ncomments: true\ntags:\n- turbulence model\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n本系列分析 OpenFOAM-3.0 版本的湍流模型。从 3.0 版开始，OpenFOAM 中的湍流模型架构发生了较大的变化，其实这种变化在 2.3 版开始已经初露端倪，在 2.3 版里，多相流的湍流模型已经开始跟单相流湍流模型分开。从 3.0 开始，单相流湍流模型和多相流湍流模型统一到了一个架构下。本系列将对 3.0 版的湍流模型进行详细的分析，分为四部分：结构概览，RTS 机制分析，编译新模型的方法，以及一些补充说明。\n\n<!--more-->\n\n#####　1.  结构概览\n\n这部分主要是概括一下湍流模型的框架的结构，如下图（请点击右键查看大图）：\n\n![湍流模型的架构](/image/turbulenceModel/turbulenceModel-3.0.png)\n\n图片中，蓝色字体的是类名，绿框中的类是调用 `declareRunTimeSeclectionTable` 的类（如果对这个的含义感兴趣，建议参考[这篇](http://xiaopingqiu.github.io/2016/03/12/RTS1/)或者[这篇](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/)），四种不同颜色的箭头，代表的是四种不同的湍流模型：单相不可压缩湍流模型，单相可压缩湍流模型，多相不可压缩模型，多相可压缩模型。\n\n在图片下面，我用了一个 `kEpsilon` 和一个 `Smagorinsky` 模型作为示例，这是因为，这两个湍流模型都是以通用形式来实现的，从 C++ 角度来说，就是模板类。通过代入不同的模板参数， `kEpsilon` 和 `Smagorinsky` 这两个模板类可以实例化为不同种类的湍流模型。\n```\ntemplate<class BasicTurbulenceModel>\nclass kEpsilon\n:\n    public eddyViscosity<RASModel<BasicTurbulenceModel> >\n{\n    // Private Member Functions\n\n        // Disallow default bitwise copy construct and assignment\n        kEpsilon(const kEpsilon&);\n        kEpsilon& operator=(const kEpsilon&);\n\n\t......\n\t......\n\n tmp<fvScalarMatrix> epsEqn\n    (\n        fvm::ddt(alpha, rho, epsilon_)\n      + fvm::div(alphaRhoPhi, epsilon_)\n      - fvm::laplacian(alpha*rho*DepsilonEff(), epsilon_)\n     ==\n        C1_*alpha*rho*G*epsilon_/k_\n      - fvm::SuSp(((2.0/3.0)*C1_ + C3_)*alpha*rho*divU, epsilon_)\n      - fvm::Sp(C2_*alpha*rho*epsilon_/k_, epsilon_)\n      + epsilonSource()\n      + fvOptions(alpha, rho, epsilon_)\n    );\n\n    epsEqn().relax();\n    fvOptions.constrain(epsEqn());\n    epsEqn().boundaryManipulate(epsilon_.boundaryField());\n    solve(epsEqn);\n    fvOptions.correct(epsilon_);\n    bound(epsilon_, this->epsilonMin_);\n\n    // Turbulent kinetic energy equation\n    tmp<fvScalarMatrix> kEqn\n    (\n        fvm::ddt(alpha, rho, k_)\n      + fvm::div(alphaRhoPhi, k_)\n      - fvm::laplacian(alpha*rho*DkEff(), k_)\n     ==\n        alpha*rho*G\n      - fvm::SuSp((2.0/3.0)*alpha*rho*divU, k_)\n      - fvm::Sp(alpha*rho*epsilon_/k_, k_)\n      + kSource()\n      + fvOptions(alpha, rho, k_)\n    );\n\t\n```\n从上述代码可以看出，输运方程中带入了密度 `rho` 和代表相体积分率的 `alpha` 。代入不同的模板参数， `rho` 和 `alpha` 的取值也会不同，从而实例化为不同的湍流模型，详细的后文还会分析。除此之外，输运方程中还加入了两种源项的实现，一种是以成员函数的形式（ `epsilonSource()` 和 `kEpsilon()` ）；另一种是以 `fvOptions` 的形式，允许用户自定义源项。\n\n在这个架构下，湍流模型是怎么通过 RTS 机制来进行选择的呢？请看下一篇。\n","slug":"TurbulenceModel-30-structure","published":1,"updated":"2016-04-24T14:38:56.963Z","layout":"post","photos":[],"link":"","_id":"cioiqege0003cz8mb1va9knwn"},{"title":"OpenFOAM-3.0 的湍流模型（四）","date":"2016-04-24T16:11:08.000Z","comments":1,"_content":"\n最后来看一个小问题：OpenFOAM-3.0 中的湍流模型是怎么编译的。在这之前，湍流模型的编译很直观，将需要编译的湍流模型的代码的 `.C` 文件写到 `Make/files` 里就好了。可是，在 OpenFOAM-3.0 里，很多湍流模型代码的 `.C` 文件并没有写到 `Make/files` 里，而是在 `makeTurbulenceModels.C`里类似这样写\n```\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n```\n然后在 `Make/files` 里写的是这个 `makeTurbulenceModels.C` 文件。为什么呢？\n\n这里来分析一下这个问题。\n\n<!--more-->\n\n这个问题，说起来也简单，不过我经过很多摸索从想明白。其实，这个问题用一个概念就可以解释清楚：条件编译。\n\n条件编译常用于头文件，格式如下\n```cpp\n//filename: kEpsilon.H\n#ifndef  kEpsilon_H\n#define  kEpsilon_H\n......\n......\n......\n#endif\n```\n这样能避免头文件重复引用的导致变量或者类重复定义的问题，原因在于，第一次 `#include \"kEpsilon.H\"` 时，会触发 `#define  kEpsilon_H` 的操作，之后如果代码中再出现 `#include \"kEpsilon.H\"` 则  `#ifndef  kEpsilon_H` 将不再成立，所以， `#ifndef ... #endif` 之间的内容将不会再度被引入。\n\nOpenFOAM 的模板类中，还常见的一种用法是\n```\n#ifdef NoRepository\n#   include \"kEpsilon.C\"\n#endif\n```\n这个怎么理解呢？ 本来也不难理解，无非就是如果定义了宏 `NoRepository` 则在头文件里将类的具体定义部分也引入到头文件里。问题是，翻遍 OpenFOAM 的 `src` 目录下的源码，也找不到 `NoRepository` 的定义。经过搜索，原来这个 `NoRepository` 是通过 g++ 的 `-D` 选项来定义的！在编译过程中，使用\n```powershell\ng++ -c -DNoRepository ...\n```\n相当于进行了 `#define NoRepository 1` 的操作，所以， `#ifdef NoRepository` 这个条件是满足的。\nOpenFOAM 中，编译选项的定义在 `wmake/rules` 下，具体的定义取决于你用的编译器，以 `linux64Gcc/c++` 为例\n```makefile\nc++WARN     = -Wall -Wextra -Wno-unused-parameter -Wold-style-cast -Wnon-virtual-dtor\n\nCC          = g++ -m64\n\ninclude $(RULES)/c++$(WM_COMPILE_OPTION)\n\nptFLAGS     = -DNoRepository -ftemplate-depth-100\n\nc++FLAGS    = $(GFLAGS) $(c++WARN) $(c++OPT) $(c++DBUG) $(ptFLAGS) $(LIB_HEADER_DIRS) -fPIC\n\nCtoo        = $(WM_SCHEDULER) $(CC) $(c++FLAGS) -c $$SOURCE -o $@\ncxxtoo      = $(Ctoo)\ncctoo       = $(Ctoo)\ncpptoo      = $(Ctoo)\n\nLINK_LIBS   = $(c++DBUG)\n\nLINKLIBSO   = $(CC) $(c++FLAGS) -shared -Xlinker --add-needed -Xlinker --no-as-needed\nLINKEXE     = $(CC) $(c++FLAGS) -Xlinker --add-needed -Xlinker --no-as-needed\n\n```\n可见这里的确是使用了 `-DNoRepository` 选项。\n\n为什么要在头文件里 `#include \"kEpsilon.C\"` 呢？这就涉及到模板类的实例化的问题。[这篇文章](http://www.codeproject.com/Articles/3515/How-To-Organize-Template-Source-Code)详细地探讨了模板类实例化过程中会遇到的问题，以及解决的办法。简单地说，**模板类不是一种类型，而是一种模板**，模板类通过代入模板参数来实例化，以得到具体的类（这个具体的类就可以看作是一种“数据类型”了）。在示例化过程中，编译器不光需要模板类的声明部分，还需要知道模板类的成员函数的具体定义部分，所以，如果在进行模板类声明的地方只包含了模板类的声明部分（一般是头文件），那实例化就会失败，编译器会报类似“undefined reference to ...” 的错误。\n[cfd-online 上的这个帖子](http://www.cfd-online.com/Forums/openfoam-programming-development/90676-norepository.html)也探讨了关于 `NoRepository` 的问题。\n\n最后，`makeTurbulenceModels.C` 里的 `#include  \"kEpsilon.H\"`，其实相当于也 `#include \"kEpsilon.C\"`，而且，要注意**这里 include 进来的是一个模板类，是用来建立实例化的模型的**！实例化的过程，在 `makeRASModel(kEpsilon)` 里，详细的参考前一篇，看看这个宏函数的展开，就知道模型参数是怎么代入进去来得到实例化的湍流模型类的。 \n\n","source":"_posts/TurbulenceModel-30-macro.md","raw":"title: \"OpenFOAM-3.0 的湍流模型（四）\"\ndate: 2016-04-25 00:11:08\ncomments: true\ntags:\n- turbulence model\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n最后来看一个小问题：OpenFOAM-3.0 中的湍流模型是怎么编译的。在这之前，湍流模型的编译很直观，将需要编译的湍流模型的代码的 `.C` 文件写到 `Make/files` 里就好了。可是，在 OpenFOAM-3.0 里，很多湍流模型代码的 `.C` 文件并没有写到 `Make/files` 里，而是在 `makeTurbulenceModels.C`里类似这样写\n```\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n```\n然后在 `Make/files` 里写的是这个 `makeTurbulenceModels.C` 文件。为什么呢？\n\n这里来分析一下这个问题。\n\n<!--more-->\n\n这个问题，说起来也简单，不过我经过很多摸索从想明白。其实，这个问题用一个概念就可以解释清楚：条件编译。\n\n条件编译常用于头文件，格式如下\n```cpp\n//filename: kEpsilon.H\n#ifndef  kEpsilon_H\n#define  kEpsilon_H\n......\n......\n......\n#endif\n```\n这样能避免头文件重复引用的导致变量或者类重复定义的问题，原因在于，第一次 `#include \"kEpsilon.H\"` 时，会触发 `#define  kEpsilon_H` 的操作，之后如果代码中再出现 `#include \"kEpsilon.H\"` 则  `#ifndef  kEpsilon_H` 将不再成立，所以， `#ifndef ... #endif` 之间的内容将不会再度被引入。\n\nOpenFOAM 的模板类中，还常见的一种用法是\n```\n#ifdef NoRepository\n#   include \"kEpsilon.C\"\n#endif\n```\n这个怎么理解呢？ 本来也不难理解，无非就是如果定义了宏 `NoRepository` 则在头文件里将类的具体定义部分也引入到头文件里。问题是，翻遍 OpenFOAM 的 `src` 目录下的源码，也找不到 `NoRepository` 的定义。经过搜索，原来这个 `NoRepository` 是通过 g++ 的 `-D` 选项来定义的！在编译过程中，使用\n```powershell\ng++ -c -DNoRepository ...\n```\n相当于进行了 `#define NoRepository 1` 的操作，所以， `#ifdef NoRepository` 这个条件是满足的。\nOpenFOAM 中，编译选项的定义在 `wmake/rules` 下，具体的定义取决于你用的编译器，以 `linux64Gcc/c++` 为例\n```makefile\nc++WARN     = -Wall -Wextra -Wno-unused-parameter -Wold-style-cast -Wnon-virtual-dtor\n\nCC          = g++ -m64\n\ninclude $(RULES)/c++$(WM_COMPILE_OPTION)\n\nptFLAGS     = -DNoRepository -ftemplate-depth-100\n\nc++FLAGS    = $(GFLAGS) $(c++WARN) $(c++OPT) $(c++DBUG) $(ptFLAGS) $(LIB_HEADER_DIRS) -fPIC\n\nCtoo        = $(WM_SCHEDULER) $(CC) $(c++FLAGS) -c $$SOURCE -o $@\ncxxtoo      = $(Ctoo)\ncctoo       = $(Ctoo)\ncpptoo      = $(Ctoo)\n\nLINK_LIBS   = $(c++DBUG)\n\nLINKLIBSO   = $(CC) $(c++FLAGS) -shared -Xlinker --add-needed -Xlinker --no-as-needed\nLINKEXE     = $(CC) $(c++FLAGS) -Xlinker --add-needed -Xlinker --no-as-needed\n\n```\n可见这里的确是使用了 `-DNoRepository` 选项。\n\n为什么要在头文件里 `#include \"kEpsilon.C\"` 呢？这就涉及到模板类的实例化的问题。[这篇文章](http://www.codeproject.com/Articles/3515/How-To-Organize-Template-Source-Code)详细地探讨了模板类实例化过程中会遇到的问题，以及解决的办法。简单地说，**模板类不是一种类型，而是一种模板**，模板类通过代入模板参数来实例化，以得到具体的类（这个具体的类就可以看作是一种“数据类型”了）。在示例化过程中，编译器不光需要模板类的声明部分，还需要知道模板类的成员函数的具体定义部分，所以，如果在进行模板类声明的地方只包含了模板类的声明部分（一般是头文件），那实例化就会失败，编译器会报类似“undefined reference to ...” 的错误。\n[cfd-online 上的这个帖子](http://www.cfd-online.com/Forums/openfoam-programming-development/90676-norepository.html)也探讨了关于 `NoRepository` 的问题。\n\n最后，`makeTurbulenceModels.C` 里的 `#include  \"kEpsilon.H\"`，其实相当于也 `#include \"kEpsilon.C\"`，而且，要注意**这里 include 进来的是一个模板类，是用来建立实例化的模型的**！实例化的过程，在 `makeRASModel(kEpsilon)` 里，详细的参考前一篇，看看这个宏函数的展开，就知道模型参数是怎么代入进去来得到实例化的湍流模型类的。 \n\n","slug":"TurbulenceModel-30-macro","published":1,"updated":"2016-04-25T01:44:22.363Z","layout":"post","photos":[],"link":"","_id":"cioiqege3003gz8mboio07i4z"},{"title":"OpenFOAM-3.0 的湍流模型（二）","date":"2016-04-24T14:21:52.000Z","comments":1,"_content":"\n本篇分析 OpenFOAM-3.0 中湍流模型的 RTS 机制。RTS 机制主要是通过调用几个相关的宏函数来实现的，所以，分析 RTS 机制需要将相关的宏函数展开。四类湍流模型，机制是类似的，这里以单相不可压缩湍流模型为例，后文会给出所有其他湍流模型相关的宏函数的展开结果，供读者参考。\nRTS 机制的基础这里不再重复了，读者若对这里涉及到 RTS 机制的名称感兴趣，可以参考我以前对 RTS 机制的解读。\n\n<!--more-->\n\n##### 2. RTS 机制\n\n单相不可压缩湍流模型类，在 `Make/files` 文件里包含的其中一个是 `src/TurbulenceModels/incompressible/turbulentTransportModels/turbulentTransportModels.C` turbulentTransportModels.C，这个文件里包含了一些通用的湍流模型模板类，并调用了一些宏函数来实现 RTS 相关的，这里将宏函数展开看看这个文件究竟实现了怎样的功能。\n\n```\nmakeBaseTurbulenceModel\n(\n    geometricOneField,\n    geometricOneField,\n    incompressibleTurbulenceModel,\n    IncompressibleTurbulenceModel,\n    transportModel\n);\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, LES, Type)\n\n// -------------------------------------------------------------------------- //\n// RAS models\n// -------------------------------------------------------------------------- //\n#include \"SpalartAllmaras.H\"\nmakeRASModel(SpalartAllmaras);\n\n#include \"kEpsilon.H\"\nmakeRASModel(kEpsilon);\n\n#include \"RNGkEpsilon.H\"\nmakeRASModel(RNGkEpsilon);\n\n#include \"realizableKE.H\"\nmakeRASModel(realizableKE);\n\n#include \"LaunderSharmaKE.H\"\nmakeRASModel(LaunderSharmaKE);\n\n#include \"kOmega.H\"\nmakeRASModel(kOmega);\n\n#include \"kOmegaSST.H\"\nmakeRASModel(kOmegaSST);\n\n#include \"kOmegaSSTSAS.H\"\nmakeRASModel(kOmegaSSTSAS);\n\n#include \"v2f.H\"\nmakeRASModel(v2f);\n\n#include \"LRR.H\"\nmakeRASModel(LRR);\n\n#include \"SSG.H\"\nmakeRASModel(SSG);\n\n// -------------------------------------------------------------------------- //\n// LES models\n// -------------------------------------------------------------------------- //\n#include \"Smagorinsky.H\"\nmakeLESModel(Smagorinsky);\n\n#include \"WALE.H\"\nmakeLESModel(WALE);\n\n#include \"dynamicLagrangian.H\"\nmakeLESModel(dynamicLagrangian);\n\n#include \"kEqn.H\"\nmakeLESModel(kEqn);\n\n#include \"dynamicKEqn.H\"\nmakeLESModel(dynamicKEqn);\n\n#include \"SpalartAllmarasDES.H\"\nmakeLESModel(SpalartAllmarasDES);\n\n#include \"SpalartAllmarasDDES.H\"\nmakeLESModel(SpalartAllmarasDDES);\n\n#include \"SpalartAllmarasIDDES.H\"\nmakeLESModel(SpalartAllmarasIDDES);\n\n#include \"DeardorffDiffStress.H\"\nmakeLESModel(DeardorffDiffStress);\n\n#include \"kOmegaSSTDES.H\"\nmakeLESModel(kOmegaSSTDES);\n\n#include \"kOmegaSSTDDES.H\"\nmakeLESModel(kOmegaSSTDDES);\n\n#include \"kOmegaSSTIDDES.H\"\nmakeLESModel(kOmegaSSTIDDES);\n\n```\n\n\n这个宏函数调用\n```\nmakeBaseTurbulenceModel\n(\n    geometricOneField,\n    geometricOneField,\n    incompressibleTurbulenceModel,\n    IncompressibleTurbulenceModel,\n    transportModel\n);\n```\n展开结果为\n```\nnamespace Foam                                                             \n{                                                                          \n    typedef TurbulenceModel                                                \n    <                                                                      \n        geometricOneField,                                                             \n        geometricOneField,                                                               \n        incompressibleTurbulenceModel,                                                         \n        transportModel                                                          \n    > transportModelincompressibleTurbulenceModel;                                                \n                                                                           \n    defineTemplateRunTimeSelectionTable // 对TurbulenceModel类中定义的 hashTable 进行了初始化                                    \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        dictionary                                                         \n    );                                                                     \n                                                                           \n    typedef IncompressibleTurbulenceModel<transportModel> transportModelIncompressibleTurbulenceModel;                     \n                                                                           \n    typedef laminar<transportModelIncompressibleTurbulenceModel> LaminartransportModelIncompressibleTurbulenceModel;   \n                                                                           \n    defineNamedTemplateTypeNameAndDebug(LaminartransportModelIncompressibleTurbulenceModel, 0); \n                                                                           \n    addToRunTimeSelectionTable   // 将laminar 模型加到定义在 TurbulenceModel 类中的hashTable                                      \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        LaminartransportModelIncompressibleTurbulenceModel,                                     \n        dictionary                                                         \n    );                                                                     \n                                                                           \n    typedef RASModel<transportModelIncompressibleTurbulenceModel> RAStransportModelIncompressibleTurbulenceModel;      \n    \n    defineNamedTemplateTypeNameAndDebug(RAStransportModelIncompressibleTurbulenceModel, 0);     \n\n     // 对 RASModel类中定义的hashTable进行了初始化                          \n    defineTemplateRunTimeSelectionTable                                    \n    (RAStransportModelIncompressibleTurbulenceModel, dictionary);                               \n    // 将 RASModel 添加到 TurbulenceModel 中定义的hashTable                         \n    addToRunTimeSelectionTable                                             \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        RAStransportModelIncompressibleTurbulenceModel,                                         \n        dictionary                                                         \n    );                                                                     \n                                                                           \n    typedef LESModel<transportModelIncompressibleTurbulenceModel> LEStransportModelIncompressibleTurbulenceModel;      \n                                                                           \n    defineNamedTemplateTypeNameAndDebug(LEStransportModelIncompressibleTurbulenceModel, 0);     \n    \n     // 对LESModel类中定义的hashTable进行了初始化\n    defineTemplateRunTimeSelectionTable                                    \n    (LEStransportModelIncompressibleTurbulenceModel, dictionary);                               \n    // 将 LESModel 添加到 TurbulenceModel 中定义的hashTable                   \n    addToRunTimeSelectionTable                                             \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        LEStransportModelIncompressibleTurbulenceModel,                                         \n        dictionary                                                         \n    );                                                                     \n}\n```\n\n`makeRASModel(SpalartAllmaras)` 展开结果为\n```\nmakeTemplatedTurbulenceModel \n (transportModelIncompressibleTurbulenceModel, RAS, SpalartAllmaras)\n```\n继续展开结果为\n\n```\ndefineNamedTemplateTypeNameAndDebug                      \n    (Foam::RASModels::SpalartAllmaras<Foam::transportModelIncompressibleTurbulenceModel>, 0);     \n                                                         \nnamespace Foam                                           \n{                                                        \n    namespace RASModels                              \n    {                                                    \n        typedef SpalartAllmaras<transportModelIncompressibleTurbulenceModel> SpalartAllmarasRAStransportModelIncompressibleTurbulenceModel;  \n                                                         \n\t// 将 SpalartAllmaras 模型添加到 RASModel 中定义的 hashTable\n        addToRunTimeSelectionTable                       \n        (                                                \n            RAStransportModelIncompressibleTurbulenceModel,                            \n            SpalartAllmarasRAStransportModelIncompressibleTurbulenceModel,                      \n            dictionary                                   \n        );                                               \n    }                                                    \n}\n```\n\n\n`makeLESModel(Smagorinsky)` 展开结果为\n```\nmakeTemplatedTurbulenceModel \n (transportModelIncompressibleTurbulenceModel, LES, Smagorinsky)\n```\n继续展开结果为\n\n```\ndefineNamedTemplateTypeNameAndDebug                      \n    (Foam::LESModels::Smagorinsky<Foam::transportModelIncompressibleTurbulenceModel>, 0);     \n                                                         \nnamespace Foam                                           \n{                                                        \n    namespace LESModels                              \n    {                                                    \n        typedef Smagorinsky<transportModelIncompressibleTurbulenceModel> SmagorinskyLEStransportModelIncompressibleTurbulenceModel;  \n                                                         \n\t// 将 Smagorinsky 模型添加到 LESModel 中定义的hashTable\n        addToRunTimeSelectionTable                       \n        (                                                \n            LEStransportModelIncompressibleTurbulenceModel,                            \n            SmagorinskyLEStransportModelIncompressibleTurbulenceModel,                      \n            dictionary                                   \n        );                                               \n    }                                                    \n}\n```\n\n注意，这里给 `kEpsilon` 和 `Smagorinsky` 模型代入的模板参数是 `transportModelIncompressibleTurbulenceModel`，即 `IncompressibleTurbulenceModel<transportModel>`。\n\n上面给出了单相不可压缩湍流模型是如何添加到各自该所属的 hashTable 里的。接着，以单相不可压缩模型为例，看看湍流模型的调用过程。\n在 `pisoFoam` 求解器中，湍流模型接口的定义如下\n```\nautoPtr<incompressible::turbulenceModel> turbulence\n(\n    incompressible::turbulenceModel::New(U, phi, laminarTransport)\n);\n// laminarTransport 是 singlePhaseTransportModel 的一个对象\n```\n`incompressible::turbulenceModel` 是 `IncompressibleTurbulenceModel<transportModel>` 的别名。\n这里调用的是 `IncompressibleTurbulenceModel` 类的 `New` 函数。\n```\ntemplate<class TransportModel>\nFoam::autoPtr<Foam::IncompressibleTurbulenceModel<TransportModel> >\nFoam::IncompressibleTurbulenceModel<TransportModel>::New\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    const TransportModel& transport,\n    const word& propertiesName\n)\n{\n    return autoPtr<IncompressibleTurbulenceModel>\n    (\n        static_cast<IncompressibleTurbulenceModel*>(\n        TurbulenceModel\n        <\n            geometricOneField,\n            geometricOneField,\n            incompressibleTurbulenceModel,\n            TransportModel\n        >::New\n        (\n            geometricOneField(),\n            geometricOneField(),\n            U,\n            phi,\n            phi,\n            transport,\n            propertiesName\n        ).ptr())\n    );\n```\n返回的是 `TurbulenceModel` 类的 `New` 函数。\n```\ntemplate\n<\n    class Alpha,\n    class Rho,\n    class BasicTurbulenceModel,\n    class TransportModel\n>\nFoam::autoPtr\n<\n    Foam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>\n>\nFoam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>::New\n(\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n{\n    // get model name, but do not register the dictionary\n    // otherwise it is registered in the database twice\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                IOobject::groupName(propertiesName, U.group()),\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).lookup(\"simulationType\")\n    );\n\n    Info<< \"Selecting turbulence model type \" << modelType << endl;\n\n    typename dictionaryConstructorTable::iterator cstrIter =\n        dictionaryConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == dictionaryConstructorTablePtr_->end())\n    {\n        FatalErrorInFunction\n            << \"Unknown TurbulenceModel type \"\n            << modelType << nl << nl\n            << \"Valid TurbulenceModel types:\" << endl\n            << dictionaryConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<TurbulenceModel>\n    (\n        cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)\n    );\n}\n```\n这里通过读取 `turbulenceProperties` 文件，查找关键字 `simulationType`（只能是 `RAS` 或  `LES` ），并据此从定义在 `TurbulenceModel` 类中 hashTable 中找对应的模型，然后返回的模型（可能是 `RAStransportModelIncompressibleTurbulenceModel`， `RASfluidThermoCompressibleTurbulenceModel`， `RASsinglePhaseTransportModelPhaseIncompressibleTurbulenceModel`， `RASphaseModelPhaseCompressibleTurbulenceModel`）的 `New` 函数\n```\ntemplate<class BasicTurbulenceModel>\nFoam::autoPtr<Foam::RASModel<BasicTurbulenceModel> >\nFoam::RASModel<BasicTurbulenceModel>::New\n(\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n{\n    // get model name, but do not register the dictionary\n    // otherwise it is registered in the database twice\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                IOobject::groupName(propertiesName, U.group()),\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).subDict(\"RAS\").lookup(\"RASModel\")\n    );\n\n    Info<< \"Selecting RAS turbulence model \" << modelType << endl;\n\n    typename dictionaryConstructorTable::iterator cstrIter =\n        dictionaryConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == dictionaryConstructorTablePtr_->end())\n    {\n        FatalErrorInFunction\n            << \"Unknown RASModel type \"\n            << modelType << nl << nl\n            << \"Valid RASModel types:\" << endl\n            << dictionaryConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<RASModel>\n    (\n        cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)\n    );\n}\n```\n这个 `New` 函数中需要读取 `RAS` 子字典，从中读取 `RASModel` 关键字，并据此从定义在 `RASModel` 类中的 hashTable 中查找到对应的湍流模型，并返回湍流模型的构造函数\n```\n return autoPtr<RASModel>\n(\n    cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)\n);\n```\n\n其他三类湍流模型，机制是类似的，这里不再详述，仅给出三个链接，供大家参考：[单相可压缩湍流模型](http://www.evernote.com/l/AYvnAYRFNFNM-bJC82rVLGiBybePgFNMF4o/)，[多相不可压缩湍流模型](http://www.evernote.com/l/AYsCXim6tA9DuYjdWDxPSTmltaMig6GIl5A/)，[多相可压缩湍流模型](http://www.evernote.com/l/AYuP9x_D59FCPbFXFTY97LbV__jBebUzW8A/)\n","source":"_posts/TurbulenceModel-30-RTS.md","raw":"title: \"OpenFOAM-3.0 的湍流模型（二）\"\ndate: 2016-04-24 22:21:52\ncomments: true\ntags:\n- turbulence model\n- Code Explained\n- RTS\ncategories:\n- OpenFOAM\n---\n\n本篇分析 OpenFOAM-3.0 中湍流模型的 RTS 机制。RTS 机制主要是通过调用几个相关的宏函数来实现的，所以，分析 RTS 机制需要将相关的宏函数展开。四类湍流模型，机制是类似的，这里以单相不可压缩湍流模型为例，后文会给出所有其他湍流模型相关的宏函数的展开结果，供读者参考。\nRTS 机制的基础这里不再重复了，读者若对这里涉及到 RTS 机制的名称感兴趣，可以参考我以前对 RTS 机制的解读。\n\n<!--more-->\n\n##### 2. RTS 机制\n\n单相不可压缩湍流模型类，在 `Make/files` 文件里包含的其中一个是 `src/TurbulenceModels/incompressible/turbulentTransportModels/turbulentTransportModels.C` turbulentTransportModels.C，这个文件里包含了一些通用的湍流模型模板类，并调用了一些宏函数来实现 RTS 相关的，这里将宏函数展开看看这个文件究竟实现了怎样的功能。\n\n```\nmakeBaseTurbulenceModel\n(\n    geometricOneField,\n    geometricOneField,\n    incompressibleTurbulenceModel,\n    IncompressibleTurbulenceModel,\n    transportModel\n);\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, LES, Type)\n\n// -------------------------------------------------------------------------- //\n// RAS models\n// -------------------------------------------------------------------------- //\n#include \"SpalartAllmaras.H\"\nmakeRASModel(SpalartAllmaras);\n\n#include \"kEpsilon.H\"\nmakeRASModel(kEpsilon);\n\n#include \"RNGkEpsilon.H\"\nmakeRASModel(RNGkEpsilon);\n\n#include \"realizableKE.H\"\nmakeRASModel(realizableKE);\n\n#include \"LaunderSharmaKE.H\"\nmakeRASModel(LaunderSharmaKE);\n\n#include \"kOmega.H\"\nmakeRASModel(kOmega);\n\n#include \"kOmegaSST.H\"\nmakeRASModel(kOmegaSST);\n\n#include \"kOmegaSSTSAS.H\"\nmakeRASModel(kOmegaSSTSAS);\n\n#include \"v2f.H\"\nmakeRASModel(v2f);\n\n#include \"LRR.H\"\nmakeRASModel(LRR);\n\n#include \"SSG.H\"\nmakeRASModel(SSG);\n\n// -------------------------------------------------------------------------- //\n// LES models\n// -------------------------------------------------------------------------- //\n#include \"Smagorinsky.H\"\nmakeLESModel(Smagorinsky);\n\n#include \"WALE.H\"\nmakeLESModel(WALE);\n\n#include \"dynamicLagrangian.H\"\nmakeLESModel(dynamicLagrangian);\n\n#include \"kEqn.H\"\nmakeLESModel(kEqn);\n\n#include \"dynamicKEqn.H\"\nmakeLESModel(dynamicKEqn);\n\n#include \"SpalartAllmarasDES.H\"\nmakeLESModel(SpalartAllmarasDES);\n\n#include \"SpalartAllmarasDDES.H\"\nmakeLESModel(SpalartAllmarasDDES);\n\n#include \"SpalartAllmarasIDDES.H\"\nmakeLESModel(SpalartAllmarasIDDES);\n\n#include \"DeardorffDiffStress.H\"\nmakeLESModel(DeardorffDiffStress);\n\n#include \"kOmegaSSTDES.H\"\nmakeLESModel(kOmegaSSTDES);\n\n#include \"kOmegaSSTDDES.H\"\nmakeLESModel(kOmegaSSTDDES);\n\n#include \"kOmegaSSTIDDES.H\"\nmakeLESModel(kOmegaSSTIDDES);\n\n```\n\n\n这个宏函数调用\n```\nmakeBaseTurbulenceModel\n(\n    geometricOneField,\n    geometricOneField,\n    incompressibleTurbulenceModel,\n    IncompressibleTurbulenceModel,\n    transportModel\n);\n```\n展开结果为\n```\nnamespace Foam                                                             \n{                                                                          \n    typedef TurbulenceModel                                                \n    <                                                                      \n        geometricOneField,                                                             \n        geometricOneField,                                                               \n        incompressibleTurbulenceModel,                                                         \n        transportModel                                                          \n    > transportModelincompressibleTurbulenceModel;                                                \n                                                                           \n    defineTemplateRunTimeSelectionTable // 对TurbulenceModel类中定义的 hashTable 进行了初始化                                    \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        dictionary                                                         \n    );                                                                     \n                                                                           \n    typedef IncompressibleTurbulenceModel<transportModel> transportModelIncompressibleTurbulenceModel;                     \n                                                                           \n    typedef laminar<transportModelIncompressibleTurbulenceModel> LaminartransportModelIncompressibleTurbulenceModel;   \n                                                                           \n    defineNamedTemplateTypeNameAndDebug(LaminartransportModelIncompressibleTurbulenceModel, 0); \n                                                                           \n    addToRunTimeSelectionTable   // 将laminar 模型加到定义在 TurbulenceModel 类中的hashTable                                      \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        LaminartransportModelIncompressibleTurbulenceModel,                                     \n        dictionary                                                         \n    );                                                                     \n                                                                           \n    typedef RASModel<transportModelIncompressibleTurbulenceModel> RAStransportModelIncompressibleTurbulenceModel;      \n    \n    defineNamedTemplateTypeNameAndDebug(RAStransportModelIncompressibleTurbulenceModel, 0);     \n\n     // 对 RASModel类中定义的hashTable进行了初始化                          \n    defineTemplateRunTimeSelectionTable                                    \n    (RAStransportModelIncompressibleTurbulenceModel, dictionary);                               \n    // 将 RASModel 添加到 TurbulenceModel 中定义的hashTable                         \n    addToRunTimeSelectionTable                                             \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        RAStransportModelIncompressibleTurbulenceModel,                                         \n        dictionary                                                         \n    );                                                                     \n                                                                           \n    typedef LESModel<transportModelIncompressibleTurbulenceModel> LEStransportModelIncompressibleTurbulenceModel;      \n                                                                           \n    defineNamedTemplateTypeNameAndDebug(LEStransportModelIncompressibleTurbulenceModel, 0);     \n    \n     // 对LESModel类中定义的hashTable进行了初始化\n    defineTemplateRunTimeSelectionTable                                    \n    (LEStransportModelIncompressibleTurbulenceModel, dictionary);                               \n    // 将 LESModel 添加到 TurbulenceModel 中定义的hashTable                   \n    addToRunTimeSelectionTable                                             \n    (                                                                      \n        transportModelincompressibleTurbulenceModel,                                              \n        LEStransportModelIncompressibleTurbulenceModel,                                         \n        dictionary                                                         \n    );                                                                     \n}\n```\n\n`makeRASModel(SpalartAllmaras)` 展开结果为\n```\nmakeTemplatedTurbulenceModel \n (transportModelIncompressibleTurbulenceModel, RAS, SpalartAllmaras)\n```\n继续展开结果为\n\n```\ndefineNamedTemplateTypeNameAndDebug                      \n    (Foam::RASModels::SpalartAllmaras<Foam::transportModelIncompressibleTurbulenceModel>, 0);     \n                                                         \nnamespace Foam                                           \n{                                                        \n    namespace RASModels                              \n    {                                                    \n        typedef SpalartAllmaras<transportModelIncompressibleTurbulenceModel> SpalartAllmarasRAStransportModelIncompressibleTurbulenceModel;  \n                                                         \n\t// 将 SpalartAllmaras 模型添加到 RASModel 中定义的 hashTable\n        addToRunTimeSelectionTable                       \n        (                                                \n            RAStransportModelIncompressibleTurbulenceModel,                            \n            SpalartAllmarasRAStransportModelIncompressibleTurbulenceModel,                      \n            dictionary                                   \n        );                                               \n    }                                                    \n}\n```\n\n\n`makeLESModel(Smagorinsky)` 展开结果为\n```\nmakeTemplatedTurbulenceModel \n (transportModelIncompressibleTurbulenceModel, LES, Smagorinsky)\n```\n继续展开结果为\n\n```\ndefineNamedTemplateTypeNameAndDebug                      \n    (Foam::LESModels::Smagorinsky<Foam::transportModelIncompressibleTurbulenceModel>, 0);     \n                                                         \nnamespace Foam                                           \n{                                                        \n    namespace LESModels                              \n    {                                                    \n        typedef Smagorinsky<transportModelIncompressibleTurbulenceModel> SmagorinskyLEStransportModelIncompressibleTurbulenceModel;  \n                                                         \n\t// 将 Smagorinsky 模型添加到 LESModel 中定义的hashTable\n        addToRunTimeSelectionTable                       \n        (                                                \n            LEStransportModelIncompressibleTurbulenceModel,                            \n            SmagorinskyLEStransportModelIncompressibleTurbulenceModel,                      \n            dictionary                                   \n        );                                               \n    }                                                    \n}\n```\n\n注意，这里给 `kEpsilon` 和 `Smagorinsky` 模型代入的模板参数是 `transportModelIncompressibleTurbulenceModel`，即 `IncompressibleTurbulenceModel<transportModel>`。\n\n上面给出了单相不可压缩湍流模型是如何添加到各自该所属的 hashTable 里的。接着，以单相不可压缩模型为例，看看湍流模型的调用过程。\n在 `pisoFoam` 求解器中，湍流模型接口的定义如下\n```\nautoPtr<incompressible::turbulenceModel> turbulence\n(\n    incompressible::turbulenceModel::New(U, phi, laminarTransport)\n);\n// laminarTransport 是 singlePhaseTransportModel 的一个对象\n```\n`incompressible::turbulenceModel` 是 `IncompressibleTurbulenceModel<transportModel>` 的别名。\n这里调用的是 `IncompressibleTurbulenceModel` 类的 `New` 函数。\n```\ntemplate<class TransportModel>\nFoam::autoPtr<Foam::IncompressibleTurbulenceModel<TransportModel> >\nFoam::IncompressibleTurbulenceModel<TransportModel>::New\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    const TransportModel& transport,\n    const word& propertiesName\n)\n{\n    return autoPtr<IncompressibleTurbulenceModel>\n    (\n        static_cast<IncompressibleTurbulenceModel*>(\n        TurbulenceModel\n        <\n            geometricOneField,\n            geometricOneField,\n            incompressibleTurbulenceModel,\n            TransportModel\n        >::New\n        (\n            geometricOneField(),\n            geometricOneField(),\n            U,\n            phi,\n            phi,\n            transport,\n            propertiesName\n        ).ptr())\n    );\n```\n返回的是 `TurbulenceModel` 类的 `New` 函数。\n```\ntemplate\n<\n    class Alpha,\n    class Rho,\n    class BasicTurbulenceModel,\n    class TransportModel\n>\nFoam::autoPtr\n<\n    Foam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>\n>\nFoam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>::New\n(\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n{\n    // get model name, but do not register the dictionary\n    // otherwise it is registered in the database twice\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                IOobject::groupName(propertiesName, U.group()),\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).lookup(\"simulationType\")\n    );\n\n    Info<< \"Selecting turbulence model type \" << modelType << endl;\n\n    typename dictionaryConstructorTable::iterator cstrIter =\n        dictionaryConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == dictionaryConstructorTablePtr_->end())\n    {\n        FatalErrorInFunction\n            << \"Unknown TurbulenceModel type \"\n            << modelType << nl << nl\n            << \"Valid TurbulenceModel types:\" << endl\n            << dictionaryConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<TurbulenceModel>\n    (\n        cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)\n    );\n}\n```\n这里通过读取 `turbulenceProperties` 文件，查找关键字 `simulationType`（只能是 `RAS` 或  `LES` ），并据此从定义在 `TurbulenceModel` 类中 hashTable 中找对应的模型，然后返回的模型（可能是 `RAStransportModelIncompressibleTurbulenceModel`， `RASfluidThermoCompressibleTurbulenceModel`， `RASsinglePhaseTransportModelPhaseIncompressibleTurbulenceModel`， `RASphaseModelPhaseCompressibleTurbulenceModel`）的 `New` 函数\n```\ntemplate<class BasicTurbulenceModel>\nFoam::autoPtr<Foam::RASModel<BasicTurbulenceModel> >\nFoam::RASModel<BasicTurbulenceModel>::New\n(\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n{\n    // get model name, but do not register the dictionary\n    // otherwise it is registered in the database twice\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                IOobject::groupName(propertiesName, U.group()),\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).subDict(\"RAS\").lookup(\"RASModel\")\n    );\n\n    Info<< \"Selecting RAS turbulence model \" << modelType << endl;\n\n    typename dictionaryConstructorTable::iterator cstrIter =\n        dictionaryConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == dictionaryConstructorTablePtr_->end())\n    {\n        FatalErrorInFunction\n            << \"Unknown RASModel type \"\n            << modelType << nl << nl\n            << \"Valid RASModel types:\" << endl\n            << dictionaryConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<RASModel>\n    (\n        cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)\n    );\n}\n```\n这个 `New` 函数中需要读取 `RAS` 子字典，从中读取 `RASModel` 关键字，并据此从定义在 `RASModel` 类中的 hashTable 中查找到对应的湍流模型，并返回湍流模型的构造函数\n```\n return autoPtr<RASModel>\n(\n    cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)\n);\n```\n\n其他三类湍流模型，机制是类似的，这里不再详述，仅给出三个链接，供大家参考：[单相可压缩湍流模型](http://www.evernote.com/l/AYvnAYRFNFNM-bJC82rVLGiBybePgFNMF4o/)，[多相不可压缩湍流模型](http://www.evernote.com/l/AYsCXim6tA9DuYjdWDxPSTmltaMig6GIl5A/)，[多相可压缩湍流模型](http://www.evernote.com/l/AYuP9x_D59FCPbFXFTY97LbV__jBebUzW8A/)\n","slug":"TurbulenceModel-30-RTS","published":1,"updated":"2016-04-25T02:15:41.650Z","layout":"post","photos":[],"link":"","_id":"cioiqeged003kz8mb0v69imzd"},{"title":"OpenFOAM-3.0 的湍流模型（三）","date":"2016-04-24T15:34:01.000Z","comments":1,"_content":"\n有了上一篇的基础，就很容易做到添加新的湍流模型了，这里分别给出对四类湍流模型增加新模型的方法。探索过程不详述了，仅给出结果。\n\n<!--more-->\n\n##### 3. 添加新模型的方法。\n添加湍流模型，关键的有两个，一是如果将新湍流模型添加到合适的 hashTable，以便能被求解器调用，另一个是 Make/files 和 Make/options 的写法以使湍流模型能被编译。\n\n这里不给出具体湍流模型的代码，仅给出 Make 的写法，以及一个 `.C` 文件。编译湍流模型的时候，新建一个目录，将需要编译的湍流模型代码、这里给出的对应类型的 `.C` 文件和 Make 文件夹都拷贝到新建的目录，然后运行 `wmake libso` 即可。\n\n###### 3.1 单相不可压缩湍流模型\n+ makeTuebulenceModels.C\n\n```cpp\n#include \"IncompressibleTurbulenceModel.H\"\n#include \"incompressible/transportModel/transportModel.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n#include \"RASModel.H\"\n#include \"LESModel.H\"\n\n\n// 宏函数定义\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, LES, Type)\n\n// -------------------------------------------------------------------------- //\n// RAS models\n// -------------------------------------------------------------------------- //\nnamespace Foam\n{\n    typedef IncompressibleTurbulenceModel<transportModel> transportModelIncompressibleTurbulenceModel; \n    \n    typedef RASModel<transportModelIncompressibleTurbulenceModel> RAStransportModelIncompressibleTurbulenceModel;    \n    \n    typedef LESModel<transportModelIncompressibleTurbulenceModel> LEStransportModelIncompressibleTurbulenceModel; \n}\n// 这里说明一下，编译新的模型，最重要的是将作为模板类的通用湍流模型代入合适的模板参数以实例化，然后将实例化的模型添加到合适的　hashTable。\n// 上面定义的几个别名，是为了下面将模型添加到 hashTable 而服务的，至于为什么要定义这几个别名，参考前一篇的 `makeBaseTurbulenceModel` 宏函数的展开部分。\n\n\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon); // 如前篇所属，这个宏函数，先对模板类进行了实例化，然后调用 `addToRunTimeSelectionTable` 宏函数，将实例化模型添加到 hashTable。\n\n// -------------------------------------------------------------------------- //\n// LES models\n// -------------------------------------------------------------------------- //\n\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n```\n+ Make/files\n```\nmakeTuebulenceModels.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestincompressibleTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/incompressible/lnInclude \\\n    -I$(LIB_SRC)/transportModels \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude \\\n\nLIB_LIBS = \\\n    -lincompressibleTransportModels \\\n    -lturbulenceModels \\\n    -lfiniteVolume \\\n    -lmeshTools\n```\n\n###### 3.2 单相可压缩湍流模型\n+ makeTurbulenceModels.C\n```cpp\n#include \"CompressibleTurbulenceModel.H\"\n#include \"compressibleTransportModel.H\"\n#include \"fluidThermo.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n#include \"ThermalDiffusivity.H\"\n#include \"EddyDiffusivity.H\"\n\n#include \"RASModel.H\"\n#include \"LESModel.H\"\n\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (fluidThermoCompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (fluidThermoCompressibleTurbulenceModel, LES, Type)\n\nnamespace Foam\n{\n    typedef ThermalDiffusivity<CompressibleTurbulenceModel<fluidThermo> >  fluidThermoCompressibleTurbulenceModel;    \n\n    typedef RASModel<EddyDiffusivity<fluidThermoCompressibleTurbulenceModel> >  RASfluidThermoCompressibleTurbulenceModel;       \n\n    typedef LESModel<EddyDiffusivity<fluidThermoCompressibleTurbulenceModel> >  LESfluidThermoCompressibleTurbulenceModel;       \n}\n\n// -------------------------------------------------------------------------- //\n// RAS models\n// -------------------------------------------------------------------------- //\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n\n#include \"mybuoyantKEpsilon.H\"\nmakeRASModel(mybuoyantKEpsilon);\n\n// -------------------------------------------------------------------------- //\n// LES models\n// -------------------------------------------------------------------------- //\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n```\n\n+ Make/files\n```\nmakeTurbulenceModels.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestcompressibleTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(LIB_SRC)/TurbulenceModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/transportModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/specie/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/solidThermo/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/solidSpecie/lnInclude \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude \\\n\nLIB_LIBS = \\\n    -lcompressibleTransportModels \\\n    -lfluidThermophysicalModels \\\n    -lsolidThermo \\\n    -lsolidSpecie \\\n    -lturbulenceModels \\\n    -lspecie \\\n    -lfiniteVolume \\\n    -lmeshTools\n```\n注意，由于在 `TurbulenceModels/compressible/lnInclude` 和 `TurbulenceModels/turbulenceModels/lnInclude` 两个目录下，都存在 `makeTurbulenceModel.H` 头文件，内容是不一样的，这里需要 include 的是前者，所以在 `Make/options` 里， `TurbulenceModels/compressible/lnInclude` 一定要写在前面才能编译成功。\n\n###### 3.3 多相不可压缩湍流模型\n+ DPMTurbulenceModels.C\n```cpp\n#include \"PhaseIncompressibleTurbulenceModel.H\"\n#include \"singlePhaseTransportModel.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n//#include \"laminar.H\"\n#include \"turbulentTransportModel.H\"\n#include \"LESModel.H\"\n\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (singlePhaseTransportModelPhaseIncompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (singlePhaseTransportModelPhaseIncompressibleTurbulenceModel, LES, Type)\n\nnamespace Foam\n{\n    typedef PhaseIncompressibleTurbulenceModel<singlePhaseTransportModel> singlePhaseTransportModelPhaseIncompressibleTurbulenceModel; \n    \n    typedef RASModel<singlePhaseTransportModelPhaseIncompressibleTurbulenceModel> RASsinglePhaseTransportModelPhaseIncompressibleTurbulenceModel;      \n    \n    typedef LESModel<singlePhaseTransportModelPhaseIncompressibleTurbulenceModel> LESsinglePhaseTransportModelPhaseIncompressibleTurbulenceModel;      \n}\n\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n```\n\n+ Make/files\n```\nDPMTurbulenceModels.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestDPMTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(LIB_SRC)/transportModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \\\n    -I$(LIB_SRC)/transportModels \\\n    -I$(LIB_SRC)/transportModels/incompressible/singlePhaseTransportModel \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/incompressible/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/phaseIncompressible/lnInclude \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude\n```\n\n注意，这里的湍流模型是给 `DPMFoam` 求解器用的，如果要给其他求解器写湍流模型，可能需要做些修改。\n\n###### 3.4 多相可压缩湍流模型\n+ phaseCompressibleTurbulenceModels.C\n```cpp\n#include \"PhaseCompressibleTurbulenceModel.H\"\n#include \"phaseModel.H\"\n#include \"twoPhaseSystem.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n#include \"ThermalDiffusivity.H\"\n#include \"EddyDiffusivity.H\"\n\n//#include \"laminar.H\"\n#include \"RASModel.H\"\n#include \"LESModel.H\"\n\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (phaseModelPhaseCompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (phaseModelPhaseCompressibleTurbulenceModel, LES, Type)\n\nnamespace Foam\n{\n    typedef ThermalDiffusivity<PhaseCompressibleTurbulenceModel<phaseModel> >  phaseModelPhaseCompressibleTurbulenceModel;  \n\n    typedef RASModel<EddyDiffusivity<phaseModelPhaseCompressibleTurbulenceModel> >  RASphaseModelPhaseCompressibleTurbulenceModel;           \n    \n    typedef LESModel<EddyDiffusivity<phaseModelPhaseCompressibleTurbulenceModel> >   LESphaseModelPhaseCompressibleTurbulenceModel;\n}\n\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n\n#include \"myphasePressureModel.H\"\nmakeTurbulenceModel\n(phaseModelPhaseCompressibleTurbulenceModel, RAS, myphasePressureModel);\n```\n\n+ Make/files\n```\nphaseCompressibleTurbulenceModels.C\nphasePressureModel/myphasePressureModel.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestphaseCompressibleTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(WM_PROJECT_DIR)/applications/solvers/multiphase/twoPhaseEulerFoam/twoPhaseSystem/lnInclude \\\n    -I$(WM_PROJECT_DIR)/applications/solvers/multiphase/twoPhaseEulerFoam/interfacialModels/lnInclude \\\n    -I$(LIB_SRC)/transportModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \\\n    -I$(LIB_SRC)/transportModels/incompressible/transportModel \\\n    -I$(LIB_SRC)/TurbulenceModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/phaseCompressible/lnInclude \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude\n```\n\n注意，这里的湍流模型是给求解器 `twoPhaseEulerFoam` 用的，如果要给其他求解器开发湍流模型，可能需要做些修改。\n","source":"_posts/TurbulenceModel-30-NewModels.md","raw":"title: \"OpenFOAM-3.0 的湍流模型（三）\"\ndate: 2016-04-24 23:34:01\ncomments: true\ntags:\n- turbulence model\n- Code Explained\ncategories:\n- OpenFOAM\n---\n\n有了上一篇的基础，就很容易做到添加新的湍流模型了，这里分别给出对四类湍流模型增加新模型的方法。探索过程不详述了，仅给出结果。\n\n<!--more-->\n\n##### 3. 添加新模型的方法。\n添加湍流模型，关键的有两个，一是如果将新湍流模型添加到合适的 hashTable，以便能被求解器调用，另一个是 Make/files 和 Make/options 的写法以使湍流模型能被编译。\n\n这里不给出具体湍流模型的代码，仅给出 Make 的写法，以及一个 `.C` 文件。编译湍流模型的时候，新建一个目录，将需要编译的湍流模型代码、这里给出的对应类型的 `.C` 文件和 Make 文件夹都拷贝到新建的目录，然后运行 `wmake libso` 即可。\n\n###### 3.1 单相不可压缩湍流模型\n+ makeTuebulenceModels.C\n\n```cpp\n#include \"IncompressibleTurbulenceModel.H\"\n#include \"incompressible/transportModel/transportModel.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n#include \"RASModel.H\"\n#include \"LESModel.H\"\n\n\n// 宏函数定义\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (transportModelIncompressibleTurbulenceModel, LES, Type)\n\n// -------------------------------------------------------------------------- //\n// RAS models\n// -------------------------------------------------------------------------- //\nnamespace Foam\n{\n    typedef IncompressibleTurbulenceModel<transportModel> transportModelIncompressibleTurbulenceModel; \n    \n    typedef RASModel<transportModelIncompressibleTurbulenceModel> RAStransportModelIncompressibleTurbulenceModel;    \n    \n    typedef LESModel<transportModelIncompressibleTurbulenceModel> LEStransportModelIncompressibleTurbulenceModel; \n}\n// 这里说明一下，编译新的模型，最重要的是将作为模板类的通用湍流模型代入合适的模板参数以实例化，然后将实例化的模型添加到合适的　hashTable。\n// 上面定义的几个别名，是为了下面将模型添加到 hashTable 而服务的，至于为什么要定义这几个别名，参考前一篇的 `makeBaseTurbulenceModel` 宏函数的展开部分。\n\n\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon); // 如前篇所属，这个宏函数，先对模板类进行了实例化，然后调用 `addToRunTimeSelectionTable` 宏函数，将实例化模型添加到 hashTable。\n\n// -------------------------------------------------------------------------- //\n// LES models\n// -------------------------------------------------------------------------- //\n\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n```\n+ Make/files\n```\nmakeTuebulenceModels.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestincompressibleTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/incompressible/lnInclude \\\n    -I$(LIB_SRC)/transportModels \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude \\\n\nLIB_LIBS = \\\n    -lincompressibleTransportModels \\\n    -lturbulenceModels \\\n    -lfiniteVolume \\\n    -lmeshTools\n```\n\n###### 3.2 单相可压缩湍流模型\n+ makeTurbulenceModels.C\n```cpp\n#include \"CompressibleTurbulenceModel.H\"\n#include \"compressibleTransportModel.H\"\n#include \"fluidThermo.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n#include \"ThermalDiffusivity.H\"\n#include \"EddyDiffusivity.H\"\n\n#include \"RASModel.H\"\n#include \"LESModel.H\"\n\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (fluidThermoCompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (fluidThermoCompressibleTurbulenceModel, LES, Type)\n\nnamespace Foam\n{\n    typedef ThermalDiffusivity<CompressibleTurbulenceModel<fluidThermo> >  fluidThermoCompressibleTurbulenceModel;    \n\n    typedef RASModel<EddyDiffusivity<fluidThermoCompressibleTurbulenceModel> >  RASfluidThermoCompressibleTurbulenceModel;       \n\n    typedef LESModel<EddyDiffusivity<fluidThermoCompressibleTurbulenceModel> >  LESfluidThermoCompressibleTurbulenceModel;       \n}\n\n// -------------------------------------------------------------------------- //\n// RAS models\n// -------------------------------------------------------------------------- //\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n\n#include \"mybuoyantKEpsilon.H\"\nmakeRASModel(mybuoyantKEpsilon);\n\n// -------------------------------------------------------------------------- //\n// LES models\n// -------------------------------------------------------------------------- //\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n```\n\n+ Make/files\n```\nmakeTurbulenceModels.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestcompressibleTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(LIB_SRC)/TurbulenceModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/transportModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/specie/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/solidThermo/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/solidSpecie/lnInclude \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude \\\n\nLIB_LIBS = \\\n    -lcompressibleTransportModels \\\n    -lfluidThermophysicalModels \\\n    -lsolidThermo \\\n    -lsolidSpecie \\\n    -lturbulenceModels \\\n    -lspecie \\\n    -lfiniteVolume \\\n    -lmeshTools\n```\n注意，由于在 `TurbulenceModels/compressible/lnInclude` 和 `TurbulenceModels/turbulenceModels/lnInclude` 两个目录下，都存在 `makeTurbulenceModel.H` 头文件，内容是不一样的，这里需要 include 的是前者，所以在 `Make/options` 里， `TurbulenceModels/compressible/lnInclude` 一定要写在前面才能编译成功。\n\n###### 3.3 多相不可压缩湍流模型\n+ DPMTurbulenceModels.C\n```cpp\n#include \"PhaseIncompressibleTurbulenceModel.H\"\n#include \"singlePhaseTransportModel.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n//#include \"laminar.H\"\n#include \"turbulentTransportModel.H\"\n#include \"LESModel.H\"\n\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (singlePhaseTransportModelPhaseIncompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (singlePhaseTransportModelPhaseIncompressibleTurbulenceModel, LES, Type)\n\nnamespace Foam\n{\n    typedef PhaseIncompressibleTurbulenceModel<singlePhaseTransportModel> singlePhaseTransportModelPhaseIncompressibleTurbulenceModel; \n    \n    typedef RASModel<singlePhaseTransportModelPhaseIncompressibleTurbulenceModel> RASsinglePhaseTransportModelPhaseIncompressibleTurbulenceModel;      \n    \n    typedef LESModel<singlePhaseTransportModelPhaseIncompressibleTurbulenceModel> LESsinglePhaseTransportModelPhaseIncompressibleTurbulenceModel;      \n}\n\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n```\n\n+ Make/files\n```\nDPMTurbulenceModels.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestDPMTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(LIB_SRC)/transportModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \\\n    -I$(LIB_SRC)/transportModels \\\n    -I$(LIB_SRC)/transportModels/incompressible/singlePhaseTransportModel \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/incompressible/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/phaseIncompressible/lnInclude \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude\n```\n\n注意，这里的湍流模型是给 `DPMFoam` 求解器用的，如果要给其他求解器写湍流模型，可能需要做些修改。\n\n###### 3.4 多相可压缩湍流模型\n+ phaseCompressibleTurbulenceModels.C\n```cpp\n#include \"PhaseCompressibleTurbulenceModel.H\"\n#include \"phaseModel.H\"\n#include \"twoPhaseSystem.H\"\n#include \"addToRunTimeSelectionTable.H\"\n#include \"makeTurbulenceModel.H\"\n\n#include \"ThermalDiffusivity.H\"\n#include \"EddyDiffusivity.H\"\n\n//#include \"laminar.H\"\n#include \"RASModel.H\"\n#include \"LESModel.H\"\n\n#define makeRASModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (phaseModelPhaseCompressibleTurbulenceModel, RAS, Type)\n\n#define makeLESModel(Type)                                                     \\\n    makeTemplatedTurbulenceModel                                               \\\n    (phaseModelPhaseCompressibleTurbulenceModel, LES, Type)\n\nnamespace Foam\n{\n    typedef ThermalDiffusivity<PhaseCompressibleTurbulenceModel<phaseModel> >  phaseModelPhaseCompressibleTurbulenceModel;  \n\n    typedef RASModel<EddyDiffusivity<phaseModelPhaseCompressibleTurbulenceModel> >  RASphaseModelPhaseCompressibleTurbulenceModel;           \n    \n    typedef LESModel<EddyDiffusivity<phaseModelPhaseCompressibleTurbulenceModel> >   LESphaseModelPhaseCompressibleTurbulenceModel;\n}\n\n#include \"mykEpsilon.H\"\nmakeRASModel(mykEpsilon);\n\n#include \"mySmagorinsky.H\"\nmakeLESModel(mySmagorinsky);\n\n#include \"myphasePressureModel.H\"\nmakeTurbulenceModel\n(phaseModelPhaseCompressibleTurbulenceModel, RAS, myphasePressureModel);\n```\n\n+ Make/files\n```\nphaseCompressibleTurbulenceModels.C\nphasePressureModel/myphasePressureModel.C\n\nLIB = $(FOAM_USER_LIBBIN)/libTestphaseCompressibleTurbulenceModels\n```\n+ Make/options\n```\nEXE_INC = \\\n    -I$(WM_PROJECT_DIR)/applications/solvers/multiphase/twoPhaseEulerFoam/twoPhaseSystem/lnInclude \\\n    -I$(WM_PROJECT_DIR)/applications/solvers/multiphase/twoPhaseEulerFoam/interfacialModels/lnInclude \\\n    -I$(LIB_SRC)/transportModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \\\n    -I$(LIB_SRC)/transportModels/incompressible/transportModel \\\n    -I$(LIB_SRC)/TurbulenceModels/compressible/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \\\n    -I$(LIB_SRC)/TurbulenceModels/phaseCompressible/lnInclude \\\n    -I$(LIB_SRC)/finiteVolume/lnInclude \\\n    -I$(LIB_SRC)/meshTools/lnInclude\n```\n\n注意，这里的湍流模型是给求解器 `twoPhaseEulerFoam` 用的，如果要给其他求解器开发湍流模型，可能需要做些修改。\n","slug":"TurbulenceModel-30-NewModels","published":1,"updated":"2016-04-25T02:07:48.763Z","layout":"post","photos":[],"link":"","_id":"cioiqegem003qz8mbvvx8e26n"},{"title":"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之 TurbulenceModel","date":"2015-09-19T04:38:20.000Z","comments":1,"_content":"\n在 `kineticTheoryModel` 类的解读时前面提到过， `kineticTheoryModel` 使用了跟湍流模型一样的接口。这一篇，就来看一下 `twoPhaseEulerFoam` 中的湍流模型。  \n\n<!--more-->\n\nOpenFOAM-2.3.x 中的`twoPhaseEulerFoam` 流体相可以调用 RAS 和 LES 湍流模型，固相可以使用两种计算固相应力的“湍流模型”。\n湍流模型的调用是通过 phaseModel 来进行的，具体的过程放到最后来讲，这里先说一下最重要的 `divDevRhoReff` 函数的形式，主要有三种类型：用于固相的 `phasePressure` 和  `kineticTheoryModel` 以及用于流体相的 RAS 模型或 LES 模型，以 `kEpsilon` 模型为例。此外，在\"pEqn.H\"里，还需要用到 `pPrime()` 函数，这个函数主要是在处理颗粒相的压力时有意义，所以，在 `phasePressure` 和 `kineticTheoryModel` 两个模型中，这个函数也需要关注一下。\n\n##### 1  phasePressure\n 很显然，这个是用于固相的，只考虑所谓固相压力，所以理论上， `divDevRhoReff`函数应该是对固相动量方程没有贡献的，实际上也正是如此，其定义如下：\n```\n Foam::tmp<Foam::fvVectorMatrix>\n Foam::RASModels::phasePressureModel::divDevRhoReff\n (\n     volVectorField& U\n ) const\n {\n     return tmp<fvVectorMatrix>\n     (\n         new fvVectorMatrix\n         (\n             U,\n             this->rho_.dimensions()*dimensionSet(0, 4, -2, 0, 0)\n         )\n     );\n }\n```\n经测验，这一项对 fvVectorMatrix 的贡献是零。\n \n ```\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::phasePressureModel::pPrime() const\n{\n    return\n        g0_\n       *min\n        (\n            exp(preAlphaExp_*(this->alpha_ - alphaMax_)),\n            expMax_\n        );\n}\n\nFoam::tmp<Foam::surfaceScalarField>\nFoam::RASModels::phasePressureModel::pPrimef() const\n{\n    return\n        g0_\n       *min\n        (\n            exp(preAlphaExp_*(fvc::interpolate(this->alpha_) - alphaMax_)),\n            expMax_\n        );\n}\n```\n`phasePressure` 模型计算的“固相压力”为\n$$\npPrime = g0\\cdot \\mathrm{min}(e^{preAlphaExp\\cdot (\\varepsilon\\_s - \\varepsilon\\_{s,max})},expMax)\n$$\n注意这里的$g0$ 与 `radialModel` 中的$g_0$ 不是一个概念！\n\n\n##### 2  kineticTheory\nKTGF 模型， 代码如下：\n```\n  Foam::tmp<Foam::fvVectorMatrix>\n  Foam::RASModels::kineticTheoryModel::divDevRhoReff\n  (\n     volVectorField& U\n  ) const\n  {\n     return\n     (\n       - fvm::laplacian(this->rho_*this->nut_, U)\n       - fvc::div\n         (\n             (this->rho_*this->nut_)*dev2(T(fvc::grad(U)))\n           + ((this->rho_*lambda_)*fvc::div(this->phi_))\n            *dimensioned<symmTensor>(\"I\", dimless, symmTensor::I)\n         )\n     );\n  }\n \n\n  \nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::pPrime() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n\n\nFoam::tmp<Foam::surfaceScalarField>\nFoam::RASModels::kineticTheoryModel::pPrimef() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return fvc::interpolate\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n```\n这一部分详细的公式已在 `kineticTheoryModel` 解读部分分析了，不再赘述。\n\n\n##### 3  kEpsilon (`OpenFOAM-2.3.x/src/TurbulenceModels/turbulenceModels/RAS/kEpsilon`)\n这个代表的是RAS湍流模型。（其实还有 LES 模型，只是 RAS 与 LES 的 `divDevRhoReff`函数形式应该是一样的），函数所在代码路径为：`OpenFOAM-2.3.x/src/TurbulenceModels/turbulenceModels/eddyViscosity/eddyViscosity.C`\n\n```\n template<class BasicTurbulenceModel>\n Foam::tmp<Foam::fvVectorMatrix>\n Foam::eddyViscosity<BasicTurbulenceModel>::divDevRhoReff\n (\n     volVectorField& U\n ) const\n {\n     return\n     (\n       - fvm::laplacian(this->alpha_*this->rho_*this->nuEff(), U)\n       - fvc::div((this->alpha_*this->rho_*this->nuEff())*dev2(T(fvc::grad(U))))\n     );\n }\n```\n`kEpsilon` 类中没有重新定义 `pPrime()` 函数，而是直接继承 `PhaseCompressibleTurbulenceModel` 类中的定义，返回零，这里就不列出代码了。\n\n#### 湍流模型的调用\n湍流模型的调用过程，值得看一下，重点是看一下湍流模型类的继承派生关系，以 `kineticTheoryModel` 为例。\n`kineticTheoryModel` 类的声明和构造函数部分如下：\n```\nclass kineticTheoryModel\n:\n    public eddyViscosity\n    <\n        RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >\n    >\n{\n    ......\n}\n\nFoam::RASModels::kineticTheoryModel::kineticTheoryModel\n(\n    const volScalarField& alpha,\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& phase,\n    const word& propertiesName,\n    const word& type\n)\n:\n    eddyViscosity<RASModel<PhaseCompressibleTurbulenceModel<phaseModel> > >\n    (\n        type,\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        phase,\n        propertiesName\n    ),\n    ......\n```\n\n可见， `kineticTheoryModel` 类继承自 `eddyViscosity` 类，并且用 `RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >` 来实例化`eddyViscosity` 类中的模板参数。\n\n再来看`eddyViscosity` 类：\n```\ntemplate<class BasicTurbulenceModel>\nclass eddyViscosity\n:\n    public BasicTurbulenceModel\n{\nprotected:\n    // Protected data\n    \n        // Fields\n        volScalarField nut_;\n\n    // Protected Member Functions\n        virtual void correctNut() = 0;\n        \ntemplate<class BasicTurbulenceModel>\nFoam::eddyViscosity<BasicTurbulenceModel>::eddyViscosity\n(\n    const word& modelName,\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    BasicTurbulenceModel\n    (\n        modelName,\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        transport,\n        propertiesName\n    ),\n\n    nut_\n    (\n        IOobject\n        (\n            IOobject::groupName(\"nut\", U.group()),\n            this->runTime_.timeName(),\n            this->mesh_,\n            IOobject::MUST_READ,\n            IOobject::AUTO_WRITE\n        ),\n        this->mesh_\n    )\n{}\n```\n注意，这里有意思的来了， `eddyViscosity` 类继承自其模板参数代表的类，具体继承自哪个类，要等模板实例化了才知道。这种用法我还是头一次接触。根据上面 `kineticTheoryModel` 类的构造函数，可知 `eddyViscosity` 类在当前分析的情况下，将继承自 `RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >` 。\n\n继续看 `RASModel` 类的定义：\n```\ntemplate<class BasicTurbulenceModel>\nclass RASModel\n:\n    public BasicTurbulenceModel\n{\n\nprotected:\n\n    // Protected data\n\n        //- RAS coefficients dictionary\n        dictionary RASDict_;\n\n        //- Turbulence on/off flag\n        Switch turbulence_;\n\n        //- Flag to print the model coeffs at run-time\n        Switch printCoeffs_;\n\n        //- Model coefficients dictionary\n        dictionary coeffDict_;\n\n        //- Lower limit of k\n        dimensionedScalar kMin_;\n\n        //- Lower limit of epsilon\n        dimensionedScalar epsilonMin_;\n\n        //- Lower limit for omega\n        dimensionedScalar omegaMin_;\n      \n       ......\n};\n\n// constructor\ntemplate<class BasicTurbulenceModel>\nFoam::RASModel<BasicTurbulenceModel>::RASModel\n(\n    const word& type,\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    BasicTurbulenceModel\n    (\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        transport,\n        propertiesName\n    ),\n\n    RASDict_(this->subOrEmptyDict(\"RAS\")),\n    turbulence_(RASDict_.lookup(\"turbulence\")),\n    printCoeffs_(RASDict_.lookupOrDefault<Switch>(\"printCoeffs\", false)),\n    coeffDict_(RASDict_.subOrEmptyDict(type + \"Coeffs\")),\n\n    kMin_\n    (\n        dimensioned<scalar>::lookupOrAddToDict\n        (\n            \"kMin\",\n            RASDict_,\n            SMALL,\n            sqr(dimVelocity)\n        )\n    ),\n\n    epsilonMin_\n    (\n        dimensioned<scalar>::lookupOrAddToDict\n        (\n            \"epsilonMin\",\n            RASDict_,\n            SMALL,\n            kMin_.dimensions()/dimTime\n        )\n    ),\n\n    omegaMin_\n    (\n        dimensioned<scalar>::lookupOrAddToDict\n        (\n            \"omegaMin\",\n            RASDict_,\n            SMALL,\n            dimless/dimTime\n        )\n    )\n{\n    // Force the construction of the mesh deltaCoeffs which may be needed\n    // for the construction of the derived models and BCs\n    this->mesh_.deltaCoeffs();\n}\n```\n`RASModel` 类也是继承自模板参数代表的类，在这里分析的情况下，模板参数将实例化为 `PhaseCompressibleTurbulenceModel<phaseModel>` ，所以， `RASModel` 类也将继承自 `PhaseCompressibleTurbulenceModel<phaseModel>`。\n\n`PhaseCompressibleTurbulenceModel` 类定义如下：\n```\ntemplate<class TransportModel>\nclass PhaseCompressibleTurbulenceModel\n:\n    public TurbulenceModel\n    <\n        volScalarField,\n        volScalarField,\n        compressibleTurbulenceModel,\n        TransportModel\n    >\n{\n\npublic:\n\n    typedef volScalarField alphaField;\n    typedef volScalarField rhoField;\n    typedef TransportModel transportModel;\n    ......\n    ......\n};\n\ntemplate<class TransportModel>\nFoam::PhaseCompressibleTurbulenceModel<TransportModel>::\nPhaseCompressibleTurbulenceModel\n(\n    const volScalarField& alpha,\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    TurbulenceModel\n    <\n        volScalarField,\n        volScalarField,\n        compressibleTurbulenceModel,\n        transportModel\n    >\n    (\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        transport,\n        propertiesName\n    )\n{}\n```\n可见， `PhaseCompressibleTurbulenceModel` 类继承自 `TurbulenceModel` 类，并且要注意给 `TurbulenceModel` 的模板代入的实例化参数。\n\n继续深入，来看 `TurbulenceModel` 的定义，\n```\ntemplate\n<\n    class Alpha,\n    class Rho,\n    class BasicTurbulenceModel,\n    class TransportModel\n>\nclass TurbulenceModel\n:\n    public BasicTurbulenceModel\n{\n\npublic:\n\n    typedef Alpha alphaField;\n    typedef Rho rhoField;\n    typedef TransportModel transportModel;\n\n\nprotected:\n\n    // Protected data\n\n        const alphaField& alpha_;\n        const transportModel& transport_;\n        ......\n        ......\n};\n\ntemplate\n<\n    class Alpha,\n    class Rho,\n    class BasicTurbulenceModel,\n    class TransportModel\n>\nFoam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>::\nTurbulenceModel\n(\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    BasicTurbulenceModel\n    (\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        propertiesName\n    ),\n    alpha_(alpha),\n    transport_(transport)\n{}\n```\n `TurbulenceModel` 继承自模板的第三个参数对应的类，从 `PhaseCompressibleTurbulenceModel` 的定义可知，这里是 `compressibleTurbulenceModel` 。此外，还要注意这个类有一个数据成员是 `alpha_`，在派生类的某些地方会调用这个数据成员。\n \n接着再看， `compressibleTurbulenceModel`，\n```\nclass compressibleTurbulenceModel\n:\n    public turbulenceModel\n{\n\nprotected:\n\n    // Protected data\n\n        const volScalarField& rho_;\n    ......\n    ......\n};\n\nFoam::compressibleTurbulenceModel::compressibleTurbulenceModel\n(\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const word& propertiesName\n)\n:\n    turbulenceModel\n    (\n        U,\n        alphaRhoPhi,\n        phi,\n        propertiesName\n    ),\n    rho_(rho)\n{}\n```\n这个类继承自 `turbulenceModel` ，并且有一个数据成员 `rho_` 。\n\n最底层的是 `turbulenceModel` 类了，其定义如下：\n```\nclass turbulenceModel\n:\n    public IOdictionary\n{\n\nprotected:\n\n    // Protected data\n\n        const Time& runTime_;\n        const fvMesh& mesh_;\n\n        const volVectorField& U_;\n        const surfaceScalarField& alphaRhoPhi_;\n        const surfaceScalarField& phi_;\n\n        //- Near wall distance boundary field\n        nearWallDist y_;\n        ......\n        ......\n};\n\nFoam::turbulenceModel::turbulenceModel\n(\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const word& propertiesName\n)\n:\n    IOdictionary\n    (\n        IOobject\n        (\n            IOobject::groupName(propertiesName, U.group()),\n            U.time().constant(),\n            U.db(),\n            IOobject::MUST_READ_IF_MODIFIED,\n            IOobject::NO_WRITE\n        )\n    ),\n\n    runTime_(U.time()),\n    mesh_(U.mesh()),\n    U_(U),\n    alphaRhoPhi_(alphaRhoPhi),\n    phi_(phi),\n    y_(mesh_)\n{}\n```\n这个类里定义了数据成员 `U_`，在 `kineticTheoryModel` 类中用到了。 \n\n总结一下，湍流模型的继承派生关系如下图（看大图请右键点击图片，选“在新标签页中打开”）：\n\n![](/image/TFM/turbulenceModel.png)\n\n像上面这种“类继承其模板参数所代表的类”的用法，在 OpenFOAM 中使用很普遍，最近在看的 thermodynamics 相关的代码里也大量使用了这种模式。不知道这是不是一种 C++ 的 design pattern？这方面我的理解还很浅显。\n","source":"_posts/TFM-TurbulenceModel.md","raw":"title: \"OpenFOAM-2.3.x 中的 twoPhaseEulerFoam 解析之 TurbulenceModel\"\ndate: 2015-09-19 12:38:20\ncomments: true\ntags:\n - OpenFOAM \n - Code Explained\ncategories:\n - OpenFOAM\n---\n\n在 `kineticTheoryModel` 类的解读时前面提到过， `kineticTheoryModel` 使用了跟湍流模型一样的接口。这一篇，就来看一下 `twoPhaseEulerFoam` 中的湍流模型。  \n\n<!--more-->\n\nOpenFOAM-2.3.x 中的`twoPhaseEulerFoam` 流体相可以调用 RAS 和 LES 湍流模型，固相可以使用两种计算固相应力的“湍流模型”。\n湍流模型的调用是通过 phaseModel 来进行的，具体的过程放到最后来讲，这里先说一下最重要的 `divDevRhoReff` 函数的形式，主要有三种类型：用于固相的 `phasePressure` 和  `kineticTheoryModel` 以及用于流体相的 RAS 模型或 LES 模型，以 `kEpsilon` 模型为例。此外，在\"pEqn.H\"里，还需要用到 `pPrime()` 函数，这个函数主要是在处理颗粒相的压力时有意义，所以，在 `phasePressure` 和 `kineticTheoryModel` 两个模型中，这个函数也需要关注一下。\n\n##### 1  phasePressure\n 很显然，这个是用于固相的，只考虑所谓固相压力，所以理论上， `divDevRhoReff`函数应该是对固相动量方程没有贡献的，实际上也正是如此，其定义如下：\n```\n Foam::tmp<Foam::fvVectorMatrix>\n Foam::RASModels::phasePressureModel::divDevRhoReff\n (\n     volVectorField& U\n ) const\n {\n     return tmp<fvVectorMatrix>\n     (\n         new fvVectorMatrix\n         (\n             U,\n             this->rho_.dimensions()*dimensionSet(0, 4, -2, 0, 0)\n         )\n     );\n }\n```\n经测验，这一项对 fvVectorMatrix 的贡献是零。\n \n ```\nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::phasePressureModel::pPrime() const\n{\n    return\n        g0_\n       *min\n        (\n            exp(preAlphaExp_*(this->alpha_ - alphaMax_)),\n            expMax_\n        );\n}\n\nFoam::tmp<Foam::surfaceScalarField>\nFoam::RASModels::phasePressureModel::pPrimef() const\n{\n    return\n        g0_\n       *min\n        (\n            exp(preAlphaExp_*(fvc::interpolate(this->alpha_) - alphaMax_)),\n            expMax_\n        );\n}\n```\n`phasePressure` 模型计算的“固相压力”为\n$$\npPrime = g0\\cdot \\mathrm{min}(e^{preAlphaExp\\cdot (\\varepsilon\\_s - \\varepsilon\\_{s,max})},expMax)\n$$\n注意这里的$g0$ 与 `radialModel` 中的$g_0$ 不是一个概念！\n\n\n##### 2  kineticTheory\nKTGF 模型， 代码如下：\n```\n  Foam::tmp<Foam::fvVectorMatrix>\n  Foam::RASModels::kineticTheoryModel::divDevRhoReff\n  (\n     volVectorField& U\n  ) const\n  {\n     return\n     (\n       - fvm::laplacian(this->rho_*this->nut_, U)\n       - fvc::div\n         (\n             (this->rho_*this->nut_)*dev2(T(fvc::grad(U)))\n           + ((this->rho_*lambda_)*fvc::div(this->phi_))\n            *dimensioned<symmTensor>(\"I\", dimless, symmTensor::I)\n         )\n     );\n  }\n \n\n  \nFoam::tmp<Foam::volScalarField>\nFoam::RASModels::kineticTheoryModel::pPrime() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n\n\nFoam::tmp<Foam::surfaceScalarField>\nFoam::RASModels::kineticTheoryModel::pPrimef() const\n{\n    // Local references\n    const volScalarField& alpha = this->alpha_;\n    const volScalarField& rho = phase_.rho();\n\n    return fvc::interpolate\n    (\n        Theta_\n       *granularPressureModel_->granularPressureCoeffPrime\n        (\n            alpha,\n            radialModel_->g0(alpha, alphaMinFriction_, alphaMax_),\n            radialModel_->g0prime(alpha, alphaMinFriction_, alphaMax_),\n            rho,\n            e_\n        )\n     +  frictionalStressModel_->frictionalPressurePrime\n        (\n            alpha,\n            alphaMinFriction_,\n            alphaMax_\n        )\n    );\n}\n```\n这一部分详细的公式已在 `kineticTheoryModel` 解读部分分析了，不再赘述。\n\n\n##### 3  kEpsilon (`OpenFOAM-2.3.x/src/TurbulenceModels/turbulenceModels/RAS/kEpsilon`)\n这个代表的是RAS湍流模型。（其实还有 LES 模型，只是 RAS 与 LES 的 `divDevRhoReff`函数形式应该是一样的），函数所在代码路径为：`OpenFOAM-2.3.x/src/TurbulenceModels/turbulenceModels/eddyViscosity/eddyViscosity.C`\n\n```\n template<class BasicTurbulenceModel>\n Foam::tmp<Foam::fvVectorMatrix>\n Foam::eddyViscosity<BasicTurbulenceModel>::divDevRhoReff\n (\n     volVectorField& U\n ) const\n {\n     return\n     (\n       - fvm::laplacian(this->alpha_*this->rho_*this->nuEff(), U)\n       - fvc::div((this->alpha_*this->rho_*this->nuEff())*dev2(T(fvc::grad(U))))\n     );\n }\n```\n`kEpsilon` 类中没有重新定义 `pPrime()` 函数，而是直接继承 `PhaseCompressibleTurbulenceModel` 类中的定义，返回零，这里就不列出代码了。\n\n#### 湍流模型的调用\n湍流模型的调用过程，值得看一下，重点是看一下湍流模型类的继承派生关系，以 `kineticTheoryModel` 为例。\n`kineticTheoryModel` 类的声明和构造函数部分如下：\n```\nclass kineticTheoryModel\n:\n    public eddyViscosity\n    <\n        RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >\n    >\n{\n    ......\n}\n\nFoam::RASModels::kineticTheoryModel::kineticTheoryModel\n(\n    const volScalarField& alpha,\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& phase,\n    const word& propertiesName,\n    const word& type\n)\n:\n    eddyViscosity<RASModel<PhaseCompressibleTurbulenceModel<phaseModel> > >\n    (\n        type,\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        phase,\n        propertiesName\n    ),\n    ......\n```\n\n可见， `kineticTheoryModel` 类继承自 `eddyViscosity` 类，并且用 `RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >` 来实例化`eddyViscosity` 类中的模板参数。\n\n再来看`eddyViscosity` 类：\n```\ntemplate<class BasicTurbulenceModel>\nclass eddyViscosity\n:\n    public BasicTurbulenceModel\n{\nprotected:\n    // Protected data\n    \n        // Fields\n        volScalarField nut_;\n\n    // Protected Member Functions\n        virtual void correctNut() = 0;\n        \ntemplate<class BasicTurbulenceModel>\nFoam::eddyViscosity<BasicTurbulenceModel>::eddyViscosity\n(\n    const word& modelName,\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    BasicTurbulenceModel\n    (\n        modelName,\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        transport,\n        propertiesName\n    ),\n\n    nut_\n    (\n        IOobject\n        (\n            IOobject::groupName(\"nut\", U.group()),\n            this->runTime_.timeName(),\n            this->mesh_,\n            IOobject::MUST_READ,\n            IOobject::AUTO_WRITE\n        ),\n        this->mesh_\n    )\n{}\n```\n注意，这里有意思的来了， `eddyViscosity` 类继承自其模板参数代表的类，具体继承自哪个类，要等模板实例化了才知道。这种用法我还是头一次接触。根据上面 `kineticTheoryModel` 类的构造函数，可知 `eddyViscosity` 类在当前分析的情况下，将继承自 `RASModel<PhaseCompressibleTurbulenceModel<phaseModel> >` 。\n\n继续看 `RASModel` 类的定义：\n```\ntemplate<class BasicTurbulenceModel>\nclass RASModel\n:\n    public BasicTurbulenceModel\n{\n\nprotected:\n\n    // Protected data\n\n        //- RAS coefficients dictionary\n        dictionary RASDict_;\n\n        //- Turbulence on/off flag\n        Switch turbulence_;\n\n        //- Flag to print the model coeffs at run-time\n        Switch printCoeffs_;\n\n        //- Model coefficients dictionary\n        dictionary coeffDict_;\n\n        //- Lower limit of k\n        dimensionedScalar kMin_;\n\n        //- Lower limit of epsilon\n        dimensionedScalar epsilonMin_;\n\n        //- Lower limit for omega\n        dimensionedScalar omegaMin_;\n      \n       ......\n};\n\n// constructor\ntemplate<class BasicTurbulenceModel>\nFoam::RASModel<BasicTurbulenceModel>::RASModel\n(\n    const word& type,\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    BasicTurbulenceModel\n    (\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        transport,\n        propertiesName\n    ),\n\n    RASDict_(this->subOrEmptyDict(\"RAS\")),\n    turbulence_(RASDict_.lookup(\"turbulence\")),\n    printCoeffs_(RASDict_.lookupOrDefault<Switch>(\"printCoeffs\", false)),\n    coeffDict_(RASDict_.subOrEmptyDict(type + \"Coeffs\")),\n\n    kMin_\n    (\n        dimensioned<scalar>::lookupOrAddToDict\n        (\n            \"kMin\",\n            RASDict_,\n            SMALL,\n            sqr(dimVelocity)\n        )\n    ),\n\n    epsilonMin_\n    (\n        dimensioned<scalar>::lookupOrAddToDict\n        (\n            \"epsilonMin\",\n            RASDict_,\n            SMALL,\n            kMin_.dimensions()/dimTime\n        )\n    ),\n\n    omegaMin_\n    (\n        dimensioned<scalar>::lookupOrAddToDict\n        (\n            \"omegaMin\",\n            RASDict_,\n            SMALL,\n            dimless/dimTime\n        )\n    )\n{\n    // Force the construction of the mesh deltaCoeffs which may be needed\n    // for the construction of the derived models and BCs\n    this->mesh_.deltaCoeffs();\n}\n```\n`RASModel` 类也是继承自模板参数代表的类，在这里分析的情况下，模板参数将实例化为 `PhaseCompressibleTurbulenceModel<phaseModel>` ，所以， `RASModel` 类也将继承自 `PhaseCompressibleTurbulenceModel<phaseModel>`。\n\n`PhaseCompressibleTurbulenceModel` 类定义如下：\n```\ntemplate<class TransportModel>\nclass PhaseCompressibleTurbulenceModel\n:\n    public TurbulenceModel\n    <\n        volScalarField,\n        volScalarField,\n        compressibleTurbulenceModel,\n        TransportModel\n    >\n{\n\npublic:\n\n    typedef volScalarField alphaField;\n    typedef volScalarField rhoField;\n    typedef TransportModel transportModel;\n    ......\n    ......\n};\n\ntemplate<class TransportModel>\nFoam::PhaseCompressibleTurbulenceModel<TransportModel>::\nPhaseCompressibleTurbulenceModel\n(\n    const volScalarField& alpha,\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    TurbulenceModel\n    <\n        volScalarField,\n        volScalarField,\n        compressibleTurbulenceModel,\n        transportModel\n    >\n    (\n        alpha,\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        transport,\n        propertiesName\n    )\n{}\n```\n可见， `PhaseCompressibleTurbulenceModel` 类继承自 `TurbulenceModel` 类，并且要注意给 `TurbulenceModel` 的模板代入的实例化参数。\n\n继续深入，来看 `TurbulenceModel` 的定义，\n```\ntemplate\n<\n    class Alpha,\n    class Rho,\n    class BasicTurbulenceModel,\n    class TransportModel\n>\nclass TurbulenceModel\n:\n    public BasicTurbulenceModel\n{\n\npublic:\n\n    typedef Alpha alphaField;\n    typedef Rho rhoField;\n    typedef TransportModel transportModel;\n\n\nprotected:\n\n    // Protected data\n\n        const alphaField& alpha_;\n        const transportModel& transport_;\n        ......\n        ......\n};\n\ntemplate\n<\n    class Alpha,\n    class Rho,\n    class BasicTurbulenceModel,\n    class TransportModel\n>\nFoam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>::\nTurbulenceModel\n(\n    const alphaField& alpha,\n    const rhoField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const transportModel& transport,\n    const word& propertiesName\n)\n:\n    BasicTurbulenceModel\n    (\n        rho,\n        U,\n        alphaRhoPhi,\n        phi,\n        propertiesName\n    ),\n    alpha_(alpha),\n    transport_(transport)\n{}\n```\n `TurbulenceModel` 继承自模板的第三个参数对应的类，从 `PhaseCompressibleTurbulenceModel` 的定义可知，这里是 `compressibleTurbulenceModel` 。此外，还要注意这个类有一个数据成员是 `alpha_`，在派生类的某些地方会调用这个数据成员。\n \n接着再看， `compressibleTurbulenceModel`，\n```\nclass compressibleTurbulenceModel\n:\n    public turbulenceModel\n{\n\nprotected:\n\n    // Protected data\n\n        const volScalarField& rho_;\n    ......\n    ......\n};\n\nFoam::compressibleTurbulenceModel::compressibleTurbulenceModel\n(\n    const volScalarField& rho,\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const word& propertiesName\n)\n:\n    turbulenceModel\n    (\n        U,\n        alphaRhoPhi,\n        phi,\n        propertiesName\n    ),\n    rho_(rho)\n{}\n```\n这个类继承自 `turbulenceModel` ，并且有一个数据成员 `rho_` 。\n\n最底层的是 `turbulenceModel` 类了，其定义如下：\n```\nclass turbulenceModel\n:\n    public IOdictionary\n{\n\nprotected:\n\n    // Protected data\n\n        const Time& runTime_;\n        const fvMesh& mesh_;\n\n        const volVectorField& U_;\n        const surfaceScalarField& alphaRhoPhi_;\n        const surfaceScalarField& phi_;\n\n        //- Near wall distance boundary field\n        nearWallDist y_;\n        ......\n        ......\n};\n\nFoam::turbulenceModel::turbulenceModel\n(\n    const volVectorField& U,\n    const surfaceScalarField& alphaRhoPhi,\n    const surfaceScalarField& phi,\n    const word& propertiesName\n)\n:\n    IOdictionary\n    (\n        IOobject\n        (\n            IOobject::groupName(propertiesName, U.group()),\n            U.time().constant(),\n            U.db(),\n            IOobject::MUST_READ_IF_MODIFIED,\n            IOobject::NO_WRITE\n        )\n    ),\n\n    runTime_(U.time()),\n    mesh_(U.mesh()),\n    U_(U),\n    alphaRhoPhi_(alphaRhoPhi),\n    phi_(phi),\n    y_(mesh_)\n{}\n```\n这个类里定义了数据成员 `U_`，在 `kineticTheoryModel` 类中用到了。 \n\n总结一下，湍流模型的继承派生关系如下图（看大图请右键点击图片，选“在新标签页中打开”）：\n\n![](/image/TFM/turbulenceModel.png)\n\n像上面这种“类继承其模板参数所代表的类”的用法，在 OpenFOAM 中使用很普遍，最近在看的 thermodynamics 相关的代码里也大量使用了这种模式。不知道这是不是一种 C++ 的 design pattern？这方面我的理解还很浅显。\n","slug":"TFM-TurbulenceModel","published":1,"updated":"2015-09-25T09:15:31.359Z","layout":"post","photos":[],"link":"","_id":"cioiqegeq003uz8mbexwzcz5j"},{"title":"湍流模型中的 RTS 机制分析","date":"2016-03-12T06:25:54.000Z","comments":1,"_content":"\n有了上一篇博文的基础，就可以来填一个[坑](http://xiaopingqiu.github.io/2015/11/25/OpenFOAM-singlePhase-turbulenceModel/)了，即分析 OpenFOAM 中湍流模型框架中的 RTS 。上一篇博文，使用的程序比较简单，这里通过一个实际使用 RTS 机制的例子来加深对 RTS 的理解。\n\n<!--more-->\n经过前面对那段简单代码的分析，可以知道， `declareRunTimeSelectionTable` 宏函数的主要功能是声明了一个 `hashTable`，并定义了一个指向这个`hashTable` 的指针， 然后还声明了几个辅助的类。 `defineRunTimeSelectionTable` 这个宏函数的主要作用是对 `declareRunTimeSelectionTable` 中的 `hashTable` 指针进行了初始化。 `addToRunTimeSelectionTable` 的主要作用是将当前类的类名以及返回当前类的对象的一个函数分别作为 `hashTable` 的 key 和 value 插入到 `hashTable` 中。下面来看这些宏函数在湍流模型框架中是怎么使用的。\n\n##### 1. turbulenceModel 类\n类体中，调用 `declareRunTimeNewSelectionTable` 宏函数\n```\n declareRunTimeNewSelectionTable\n (\n    autoPtr,\n    turbulenceModel,\n    turbulenceModel,\n    (\n        const volVectorField& U,\n        const surfaceScalarField& phi,\n        transportModel& transport,\n        const word& turbulenceModelName\n    ),\n    (U, phi, transport, turbulenceModelName)\n);\n```\n注意这里用的是 `declareRunTimeNewSelectionTable`！与 `declareRunTimeSelectionTable` 区别在于，`declareRunTimeNewSelectionTable` 这个宏函数定义的插入到 `hashTable` 中的那个函数，返回值不是派生类的对象，而是派生类中的 `New` 函数的返回值！\n```\n    static autoPtr< baseType > New##baseType argList                      \\\n    {                                                                     \\\n        return autoPtr< baseType >(baseType##Type::New parList.ptr());    \\\n    }\n    \n    ......\n    if                                                                \\\n    (                                                                 \\\n       !argNames##ConstructorTablePtr_->insert                        \\\n        (                                                             \\\n            lookup,                                                   \\\n            New##baseType                                             \\\n        )                                                             \\\n    )                                                                 \\\n```\n这说明，这里的派生类 `RASModel` 将不会作为一个具体的湍流模型来使用。而是用来选择 `RAS`类型的具体湍流模型的一个跳板。\n\n类体外，调用 \n```\ndefineTypeNameAndDebug(turbulenceModel, 0);\ndefineRunTimeSelectionTable(turbulenceModel, turbulenceModel);\n```\n注意，这里没有调用 `addToRunTimeSelectionTable` 宏函数， `turbulenceModel` 类是基类，也不会作为具体的湍流模型来调用，所以不需要将它自己添加到 `hashTable`。\n\n\n#####2. RASModel\n`turbulenceModel` 类下一层的派生类是 `RASModel` 和 `LESModel` 。先来看 `RASModel`，这个类类体里调用了 `declareRunTimeSelectionTable` 。\n```\n        declareRunTimeSelectionTable\n        (\n            autoPtr,\n            RASModel,\n            dictionary,\n            (\n                const volVectorField& U,\n                const surfaceScalarField& phi,\n                transportModel& transport,\n                const word& turbulenceModelName\n            ),\n            (U, phi, transport, turbulenceModelName)\n        );\n```\n类体外调用了\n```\ndefineTypeNameAndDebug(RASModel, 0);\ndefineRunTimeSelectionTable(RASModel, dictionary);\naddToRunTimeSelectionTable(turbulenceModel, RASModel, turbulenceModel);\n```\n\n根据前面的分析，这里 RASModel 又创建了一个新的 `hashTable`， 用的是 `declareRunTimeSelectionTable` 和 `defineRunTimeSelectionTable(RASModel, dictionary);` ，同时，RASModel类 本身又添加到了 `turbulenceModel` 中建立的 `hashTable` 里： `addToRunTimeSelectionTable(turbulenceModel, RASModel, turbulenceModel);` \n\n`RASModel` 类之下的派生类，就是具体的湍流模型了，这里以 `kEpsilon` 模型为例：\n\n+ kEpsilon\n具体的湍流模型，如`kEpsilon` ，只需要添加到上面基类中创建的 `hashTable` 中，就能保证其能被调用到。\n```\naddToRunTimeSelectionTable(RASModel, kEpsilon, dictionary);\n```\n这里是添加到了 RASModel 中创建的 `hashTable` 里。\n\n##### 3. LESModel\n`LESModel` 类也是继承自 `turbulenceModel` 的，所以其处理方法跟 `RASModel` 是一样的。不过 `LES` 类的模型的继承关系略比 `RAS` 类的复杂一点（参看[这篇](http://xiaopingqiu.github.io/2015/11/25/OpenFOAM-singlePhase-turbulenceModel/)中的 LES 模型继承关系图）。在这个图中，中间层的 `GenEddyVisc` 等4个虚线框中的类不是作为具体的湍流模型来调用的，这里有必要看一下这样的中间类在 RTS 机制中是怎么处理的。检查这几个类的代码，可以发现 `GenEddyVisc` 和 `GenSGSStress` 中只是在类体外调用了 `defineTypeNameWithName(GenEddyVisc, \"GenEddyVisc\");` ， `scaleSimilarity` 在类体中调用了 `TypeName`，类体外调用了 `defineTypeNameAndDebug` ，`DESModel` 中没有任何处理。可见这些类只是对 `typeName` 做了处理，并没有调用 `addToRunTimeSelectionTable` 。\n同 `RASModel` 一样， `LESModel` 之下派生的具体的湍流模型则需要调用 `addToRunTimeSelectionTable` 来将自己添加到 `LESModel` 中定义的 `hashTable` 中。\n\n由上可知，湍流模型的调用过程大致是这样的：\n求解器里创建一个 `turbulenceModel` 类型的 `autoPtr`，并调用 `turbulenceModel::New` 来初始化。 `turbulenceModel::New` 从 turbulenceProperties 文件中读取关键字，假设读取到 simulationType 为 `RASModel` ，则 `turbulenceModel::New` 的 `cstrIter()` 返回的是 `RASModel::New`，于是 `cstrIter()(U, phi, transport, turbulenceModelName)` 则是在调用 `RASModel::New`。然后， `RASModel::New` 从 RASProperties 文件里读取关键字，并根据读取到的内容，从 `RASModel` 类中创建的 `hashTable` 里查找对应的湍流模型，假设从 RASProperties 中读取到的是 `kEpsilon`， 则返回一个 `kEpsilon` 模型的对象。最终结果是，求解器里创建的`turbulenceModel` 类型的 `autoPtr` 指向了 `kEpsilon` 类的对象，这就实现了对 `kEpsilon` 模型的调用。\n","source":"_posts/RTS2.md","raw":"title: \"湍流模型中的 RTS 机制分析\"\ndate: 2016-03-12 14:25:54\ncomments: true\ntags:\n- Code Explained\n- RTS\n- turbulence model\ncategories:\n- OpenFOAM\n---\n\n有了上一篇博文的基础，就可以来填一个[坑](http://xiaopingqiu.github.io/2015/11/25/OpenFOAM-singlePhase-turbulenceModel/)了，即分析 OpenFOAM 中湍流模型框架中的 RTS 。上一篇博文，使用的程序比较简单，这里通过一个实际使用 RTS 机制的例子来加深对 RTS 的理解。\n\n<!--more-->\n经过前面对那段简单代码的分析，可以知道， `declareRunTimeSelectionTable` 宏函数的主要功能是声明了一个 `hashTable`，并定义了一个指向这个`hashTable` 的指针， 然后还声明了几个辅助的类。 `defineRunTimeSelectionTable` 这个宏函数的主要作用是对 `declareRunTimeSelectionTable` 中的 `hashTable` 指针进行了初始化。 `addToRunTimeSelectionTable` 的主要作用是将当前类的类名以及返回当前类的对象的一个函数分别作为 `hashTable` 的 key 和 value 插入到 `hashTable` 中。下面来看这些宏函数在湍流模型框架中是怎么使用的。\n\n##### 1. turbulenceModel 类\n类体中，调用 `declareRunTimeNewSelectionTable` 宏函数\n```\n declareRunTimeNewSelectionTable\n (\n    autoPtr,\n    turbulenceModel,\n    turbulenceModel,\n    (\n        const volVectorField& U,\n        const surfaceScalarField& phi,\n        transportModel& transport,\n        const word& turbulenceModelName\n    ),\n    (U, phi, transport, turbulenceModelName)\n);\n```\n注意这里用的是 `declareRunTimeNewSelectionTable`！与 `declareRunTimeSelectionTable` 区别在于，`declareRunTimeNewSelectionTable` 这个宏函数定义的插入到 `hashTable` 中的那个函数，返回值不是派生类的对象，而是派生类中的 `New` 函数的返回值！\n```\n    static autoPtr< baseType > New##baseType argList                      \\\n    {                                                                     \\\n        return autoPtr< baseType >(baseType##Type::New parList.ptr());    \\\n    }\n    \n    ......\n    if                                                                \\\n    (                                                                 \\\n       !argNames##ConstructorTablePtr_->insert                        \\\n        (                                                             \\\n            lookup,                                                   \\\n            New##baseType                                             \\\n        )                                                             \\\n    )                                                                 \\\n```\n这说明，这里的派生类 `RASModel` 将不会作为一个具体的湍流模型来使用。而是用来选择 `RAS`类型的具体湍流模型的一个跳板。\n\n类体外，调用 \n```\ndefineTypeNameAndDebug(turbulenceModel, 0);\ndefineRunTimeSelectionTable(turbulenceModel, turbulenceModel);\n```\n注意，这里没有调用 `addToRunTimeSelectionTable` 宏函数， `turbulenceModel` 类是基类，也不会作为具体的湍流模型来调用，所以不需要将它自己添加到 `hashTable`。\n\n\n#####2. RASModel\n`turbulenceModel` 类下一层的派生类是 `RASModel` 和 `LESModel` 。先来看 `RASModel`，这个类类体里调用了 `declareRunTimeSelectionTable` 。\n```\n        declareRunTimeSelectionTable\n        (\n            autoPtr,\n            RASModel,\n            dictionary,\n            (\n                const volVectorField& U,\n                const surfaceScalarField& phi,\n                transportModel& transport,\n                const word& turbulenceModelName\n            ),\n            (U, phi, transport, turbulenceModelName)\n        );\n```\n类体外调用了\n```\ndefineTypeNameAndDebug(RASModel, 0);\ndefineRunTimeSelectionTable(RASModel, dictionary);\naddToRunTimeSelectionTable(turbulenceModel, RASModel, turbulenceModel);\n```\n\n根据前面的分析，这里 RASModel 又创建了一个新的 `hashTable`， 用的是 `declareRunTimeSelectionTable` 和 `defineRunTimeSelectionTable(RASModel, dictionary);` ，同时，RASModel类 本身又添加到了 `turbulenceModel` 中建立的 `hashTable` 里： `addToRunTimeSelectionTable(turbulenceModel, RASModel, turbulenceModel);` \n\n`RASModel` 类之下的派生类，就是具体的湍流模型了，这里以 `kEpsilon` 模型为例：\n\n+ kEpsilon\n具体的湍流模型，如`kEpsilon` ，只需要添加到上面基类中创建的 `hashTable` 中，就能保证其能被调用到。\n```\naddToRunTimeSelectionTable(RASModel, kEpsilon, dictionary);\n```\n这里是添加到了 RASModel 中创建的 `hashTable` 里。\n\n##### 3. LESModel\n`LESModel` 类也是继承自 `turbulenceModel` 的，所以其处理方法跟 `RASModel` 是一样的。不过 `LES` 类的模型的继承关系略比 `RAS` 类的复杂一点（参看[这篇](http://xiaopingqiu.github.io/2015/11/25/OpenFOAM-singlePhase-turbulenceModel/)中的 LES 模型继承关系图）。在这个图中，中间层的 `GenEddyVisc` 等4个虚线框中的类不是作为具体的湍流模型来调用的，这里有必要看一下这样的中间类在 RTS 机制中是怎么处理的。检查这几个类的代码，可以发现 `GenEddyVisc` 和 `GenSGSStress` 中只是在类体外调用了 `defineTypeNameWithName(GenEddyVisc, \"GenEddyVisc\");` ， `scaleSimilarity` 在类体中调用了 `TypeName`，类体外调用了 `defineTypeNameAndDebug` ，`DESModel` 中没有任何处理。可见这些类只是对 `typeName` 做了处理，并没有调用 `addToRunTimeSelectionTable` 。\n同 `RASModel` 一样， `LESModel` 之下派生的具体的湍流模型则需要调用 `addToRunTimeSelectionTable` 来将自己添加到 `LESModel` 中定义的 `hashTable` 中。\n\n由上可知，湍流模型的调用过程大致是这样的：\n求解器里创建一个 `turbulenceModel` 类型的 `autoPtr`，并调用 `turbulenceModel::New` 来初始化。 `turbulenceModel::New` 从 turbulenceProperties 文件中读取关键字，假设读取到 simulationType 为 `RASModel` ，则 `turbulenceModel::New` 的 `cstrIter()` 返回的是 `RASModel::New`，于是 `cstrIter()(U, phi, transport, turbulenceModelName)` 则是在调用 `RASModel::New`。然后， `RASModel::New` 从 RASProperties 文件里读取关键字，并根据读取到的内容，从 `RASModel` 类中创建的 `hashTable` 里查找对应的湍流模型，假设从 RASProperties 中读取到的是 `kEpsilon`， 则返回一个 `kEpsilon` 模型的对象。最终结果是，求解器里创建的`turbulenceModel` 类型的 `autoPtr` 指向了 `kEpsilon` 类的对象，这就实现了对 `kEpsilon` 模型的调用。\n","slug":"RTS2","published":1,"updated":"2016-03-12T08:21:00.646Z","layout":"post","photos":[],"link":"","_id":"cioiqegex003yz8mb55c4967x"},{"title":"OpenFOAM 中的 Run Time Selection 机制","date":"2016-03-12T05:06:48.000Z","comments":1,"_content":"\n[source flux 博客](http://www.sourceflux.de/blog/series/rts-2/) 曾经出过一个解释 Run Time Selection(RTS) 机制的系列博文，推荐想理解 RTS 的读者去仔细读读。本篇算是我在读完以后做的一个笔记，以及一些总结，供读者参考。\n\n<!--more-->\n\nOpenFOAM 中包含各个 CFD 相关的模块，每个模块，从 C++ 的角度来看，其实都是一个类的框架。基类用作接口，一个派生类则是一个具体的模型。OpenFOAM 中的模块广泛使用 RTS 机制，因此 OpenFOAM 的求解器中，只需要设定模型的调用接口。算例具体使用的是那个模型，则是在运行时才确定的，而且可以在算例运行过程中修改选中的模型。下面通过一个 [source flux 博客](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/) 提供的代码，来解读 RTS 机制的实现原理。\n为了方便解读，这里将代码摘录如下，代码所有权归 [source flux 博客](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/) 所有：\n\n```\n#include \"word.H\"\n#include \"messageStream.H\"\n#include \"argList.H\"\n\nusing namespace Foam;\n\n#include \"typeInfo.H\"\n#include \"runTimeSelectionTables.H\"\n#include \"addToRunTimeSelectionTable.H\"\n\n// Main program:\nclass AlgorithmBase\n{\n    public: \n\n        // Declare the static variable typeName of the class AlgorithmBase.\n        TypeName (\"base\");\n\n        // Empty constructor. \n        AlgorithmBase () {};\n\n        // Word constructor.\n        AlgorithmBase (const word& algorithmName) {};\n\n        // Destructor: needs to be declared virtual since \n        virtual ~AlgorithmBase() {};\n\n        // Macro for declaring stuff required for RTS \n        declareRunTimeSelectionTable\n        (\n            autoPtr, \n            AlgorithmBase, \n            Word, \n            (\n                const word& algorithmName\n            ),\n            (algorithmName)\n        )\n\n        // static Factory Method (selector)\n        static autoPtr<AlgorithmBase> New (const word& algorithmName)\n        {\n\n            // Find the Factory Method pointer in the RTS Table \n            // (HashTable<word, autoPtr<AlgorithmBase>(*)(word))\n            WordConstructorTable::iterator cstrIter =\n                WordConstructorTablePtr_->find(algorithmName);\n\n            // If the Factory Method was not found. \n            if (cstrIter == WordConstructorTablePtr_->end())\n            {\n                FatalErrorIn\n                (\n                    \"AlgorithmBase::New(const word&)\"\n                )   << \"Unknown AlgorithmBase type \"\n                    << algorithmName << nl << nl\n                    << \"Valid AlgorithmBase types are :\" << endl\n                    << WordConstructorTablePtr_->sortedToc()\n                    << exit(FatalError);\n            }\n\n            // Call the \"constructor\" and return the autoPtr<AlgorithmBase>\n            return cstrIter()(algorithmName);\n\n        }\n\n        // Make the class callable (function object) \n        virtual void operator()() \n        {\n            // Overridable default implementation\n            Info << \"AlgorithmBase::operator()()\" << endl;\n        }\n};\n\ndefineTypeNameAndDebug(AlgorithmBase, 0);\ndefineRunTimeSelectionTable(AlgorithmBase, Word);\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmBase, Word);\n\nclass AlgorithmNew\n:\n    public AlgorithmBase\n{\n    public: \n\n        // Declare the static variable typeName of the class AlgorithmNew.\n        TypeName (\"new\");\n\n        // Empty constructor. \n        AlgorithmNew () {};\n\n        // Word constructor.\n        AlgorithmNew (const word& algorithmName) {};\n\n        // Make the class callable (function object) \n        virtual void operator()()\n        {\n            Info << \"AlgorithmNew::operator()()\" << endl;\n        }\n};\n\ndefineTypeNameAndDebug(AlgorithmNew, 0);\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmNew , Word);\n\nclass AlgorithmAdditional\n:\n    public AlgorithmNew \n{\n    public: \n\n        // Declare the static variable typeName of the class AlgorithmAdditional.\n        TypeName (\"additional\");\n\n        // Empty constructor. \n        AlgorithmAdditional () {};\n\n        // Word constructor.\n        AlgorithmAdditional (const word& algorithmName) {};\n\n        // Make the class callable (function object) \n        virtual void operator()()\n        {\n            // Call base operator explicitly.\n            AlgorithmNew::operator()();\n            // Perform additional operations.\n            Info << \"AlgorithmAdditional::operator()()\" << endl;\n        }\n};\n\ndefineTypeNameAndDebug(AlgorithmAdditional, 0);\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmAdditional , Word);\n\nint main(int argc, char *argv[])\n{\n    argList::addOption\n    (\n        \"algorithmName\",\n        \"name of the run-time selected algorithm\"\n    );\n\n    argList args(argc, argv);\n\n    if (args.optionFound(\"algorithmName\"))\n    {\n        // Get the name of the algorithm from the arguments passed to the\n        // application. \n        const word algorithmName = args.option(\"algorithmName\");\n\n        // RTS call. \n        autoPtr<AlgorithmBase> algorithmPtr = AlgorithmBase::New(algorithmName);\n\n        // Get the reference to the algorithm from the smart pointer.\n        AlgorithmBase& algorithm = algorithmPtr(); \n\n        // Call the algorithm.\n        algorithm(); \n    }\n    else\n    {\n        FatalErrorIn\n        (\n            \"main()\"\n        )   << \"Please use with the 'algorithmName' option.\" << endl\n            << exit(FatalError);\n    }\n\n    Info<< \"\\nEnd\\n\" << endl;\n\n    return 0;\n}\n```\n\n在解读原理之前，先来看看这段代码。可以发现，RTS 机制的实现跟几个函数的调用有关： `declareRunTimeSelectionTable`， `defineRunTimeSelectionTable`， `defineTypeNameAndDebug`， `addToRunTimeSelectionTable`。规律可以总结如下：\n1. 基类类体里调用 `TypeName` 和 `declareRunTimeSelectionTable` 两个函数，类体外面调用 `defineTypeNameAndDebug` ， `defineRunTimeSelectionTable` 和 `addToRunTimeSelectionTable` 三个函数；\n2. 基类中需要一个静态 `New` 函数作为 `selector`。 \n3. 派生类类体中需要调用 `TypeName` 函数，类体外调用 `defineRunTimeSelectionTable` 和 `addToRunTimeSelectionTable` 两个宏函数。\n\n以上函数，经过搜索，发现都是定义在 `runTimeSelectionTables.H` 和 `addToRunTimeSelectionTable.H` 两个头文件中，而且，这些函数都是宏函数。\n\n看来，理解 RTS 的第一步就需要仔细看看这几个宏函数。\n\n先来看基类中的宏函数 `declareRunTimeSelectionTable` ，根据 [source flux 的博文](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/)，这个宏函数针对前面的那段代码的展开结果为：\n\n```\n    typedef autoPtr< AlgorithmBase > (*WordConstructorPtr)( const word& algorithmName );\n   \n    typedef HashTable< WordConstructorPtr, word, string::hash > WordConstructorTable;\n    static WordConstructorTable* WordConstructorTablePtr_;\n    static void constructWordConstructorTables();\n    static void destroyWordConstructorTables();\n    template< class AlgorithmBaseType >\n    class addWordConstructorToTable\n    {\n        public:\n        static autoPtr< AlgorithmBase > New ( const word& algorithmName )\n        {\n            return autoPtr< AlgorithmBase >(new AlgorithmBaseType (algorithmName));\n        }\n        addWordConstructorToTable ( const word& lookup = AlgorithmBaseType::typeName )\n        {\n            constructWordConstructorTables();\n            if (!WordConstructorTablePtr_->insert(lookup, New))\n            {\n                std::cerr<< \"Duplicate entry \"\n                    << lookup << \" in runtime selection table \"\n                    << \"AlgorithmBase\" << std::endl;\n                error::safePrintStack(std::cerr);\n            }\n        }\n        \n        ~addWordConstructorToTable()\n        {\n            destroyWordConstructorTables();\n        }\n    };\n    template< class AlgorithmBaseType >\n    class addRemovableWordConstructorToTable\n    {\n        const word& lookup_;\n        \n        public:\n        static autoPtr< AlgorithmBase > New ( const word& algorithmName )\n        {\n            return autoPtr< AlgorithmBase >(new AlgorithmBaseType (algorithmName));\n        }\n        addRemovableWordConstructorToTable ( const word& lookup = AlgorithmBaseType::typeName )\n        : lookup_(lookup)\n        {\n            constructWordConstructorTables();\n            WordConstructorTablePtr_->set(lookup, New);\n        }\n       \n        ~addRemovableWordConstructorToTable()\n        {\n            if (WordConstructorTablePtr_)\n            {\n                WordConstructorTablePtr_->erase(lookup_);\n            }\n        }\n    };\n```\n注意，由于 `declareRunTimeSelectionTable` 是在基类类体里调用的，所以，以上内容都是在类体里的。这相当于在类体了定义了两个 `typedef`，一个静态数据成员，两个静态函数，还有两个类。\n先来看这两个 `typedef` 。第一个，定义的是一个函数指针，这样定义的结果是， `WordConstructorPtr` 代表一个指向参数为 `const word&`，返回类型为 `autoPtr< AlgorithmBase >` 的函数指针。第二个好理解，将一个 key 和 value 分别为 `word` 和 `WordConstructorPtr` 的 `hashTable` 定义了一个别名 `WordConstructorTable` 。\n静态数据成员 `WordConstructorTablePtr_` 是一个 `WordConstructorTable` 类型的指针。\n两个静态成员函数，这里只是声明了，并且注意到在下面定义的两个类中用到了这两个函数。\n\n继续看 `defineRunTimeSelectionTable(AlgorithmBase, Word) `。这个宏展开的结果为：\n```\nAlgorithmBase::WordConstructorTable* AlgorithmBase::WordConstructorTablePtr_ = __null;\n    void AlgorithmBase::constructWordConstructorTables() {\n        static bool constructed = false;\n        if (!constructed) {\n            constructed = true;\n            AlgorithmBase::WordConstructorTablePtr_ = new AlgorithmBase::WordConstructorTable;\n        }\n    };\n    \n    void AlgorithmBase::destroyWordConstructorTables() {\n        if (AlgorithmBase::WordConstructorTablePtr_) {\n            delete AlgorithmBase::WordConstructorTablePtr_;\n            AlgorithmBase::WordConstructorTablePtr_ = __null;\n        }\n    };\n```\n这个宏函数的主要功能，是对 `declareRunTimeSelectionTable` 中定义的静态数据成员和两个静态函数进行了定义。首先对静态数据成员 `WordConstructorTablePtr_` 初始化为 `__null`，然后 `constructWordConstructorTables` 函数将 `WordConstructorTablePtr_` 指向一个动态分配的 `WordConstructorTable` 。 `destroyWordConstructorTables` 则是对指针 `WordConstructorTablePtr_` 进行销毁。\n\n接着， `addToRunTimeSelectionTable(AlgorithmBase, AlgorithmBase, Word)` ，这宏函数展开以后其实就一句话：\n```\nAlgorithmBase::addWordConstructorToTable< AlgorithmBase > addAlgorithmBaseWordConstructorToAlgorithmBaseTable_;\n```\n这个语句，定义了一个 `addWordConstructorToTable` 的对象，仅此而已。但是，注意在创建一个类的对象的时候，是要调用该类的构造函数的。回头看 `addWordConstructorToTable` 类的构造函数，有意思的地方出现了。这个类的构造函数中，首先调用了 `constructWordConstructorTables` 函数，即对指针 `WordConstructorTablePtr_` 进行了初始化。然后，对 `WordConstructorTablePtr_` 进行 `insert` 操作，即，往其指向的 `hashTable` 插入 `key-value` 对。这里的 key 是创建对象 `addAlgorithmBaseWordConstructorToAlgorithmBaseTable_` 时代入的模板参数对应的 类的 `typeName`（这一句很长很绕，需要好好理解，因为很重要！），value 则是  `New` 函数。这个 `New` 函数，指的是定义在 `addWordConstructorToTable` 中的 `New` 函数。这个 `New` 函数非常重要，再写一遍：\n```\nstatic autoPtr< AlgorithmBase > New ( const word& algorithmName )\n{\n    return autoPtr< AlgorithmBase >(new AlgorithmBaseType (algorithmName));\n}\n```\n这个`New` 函数，返回的是一个 `AlgorithmBaseType`（这里是 AlgorithmBase ） 类型的临时对象的指针！对应这里的情形，现在可以知道这个 `insert` 操作将创建一个 “类的typeName -- 返回类的临时对象的引用的函数” 映射对，并增加到 `WordConstructorTablePtr_` 中（看来 `ddToRunTimeSelectionTable` 中创建一个 `addWordConstructorToTable` 类的对象，居然目的是为了调用其构造函数。）。如果 `insert` 操作失败（原因是想要插入的 key 与 `hashTable` 已有的重复了，所以每一个类都需要不同的 typeName！），就会报条目重复的错。\n\n好了，看完了基类相关的，在往下看派生类。前文已讲，派生类只需要在类体里调用 `TypeName`，然后在类体外调用 `addToRunTimeSelectionTable` 。对于派生类 `AlgorithmNew`，我们来看其具体的调用语句是\n```\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmNew , Word);\n```\n展开的结果应该是\n```\nAlgorithmBase::addWordConstructorToTable< AlgorithmNew > addAlgorithmNewWordConstructorToAlgorithmBaseTable_;\n```\n注意，这里又创建了一个 `addWordConstructorToTable` 类的对象，只是这里代入的模板参数是 `AlgorithmNew` 。于是，调用类的构造函数时代入的模板参数也就变了，所以这时 `New` 函数返回的将是 `AlgorithmNew` 类的临时对象的指针。并且， AlgorithmNew 这个名字与其对应的 `New` 函数组成的映射对，也被 `insert` 到 `WordConstructorTablePtr_` 里面。\n\n而 `AlgorithmAdditional` 这个类，虽然是继承自 `AlgorithmNew` ，但是也是间接继承 `AlgorithmBase` 。并且，在 `AlgorithmAdditional` 类的类体之后调用的宏函数 `addToRunTimeSelectionTable(AlgorithmBase, AlgorithmAdditional , Word)`  ，依然是将构建的映射对添加到了同一个 `hashTable` 里。\n\n最后，再来看一下 `selector`，即基类中定义的 `New` 函数。这个函数的返回值类型为 `autoPtr<AlgorithmBase>` ，参数为跟类的 `typeName` 一样，都是 `word&`。这个函数里面，首先定义了一个 `hashTable` 的迭代器 `cstrIter` ，利用迭代器来遍历搜索，看 `WordConstructorTable` 里面是否能找到参数 `algorithmName` 相符的 key 值，如果找不到，那就报错退出，并输出当前的 `WordConstructorTable` 中可选的项的名称（即 WordConstructorTablePtr_->sortedToc()）；如果找到了，那就返回这个 key 对应的 value。而 `WordConstructorTable` 的 value 是一个函数指针，所以 `cstrIter()` 返回的是 `algorithmName` 对应的那个 `New` 函数（不要跟基类 `AlgorithmBase` 中作为 selector 的 `New` 函数搞混了！）。进一步看， `cstrIter()(algorithmName)` 则表示的是函数调用了，传给函数的参数正是 `algorithmName`！\n**所以， `cstrIter()(algorithmName)` 返回的是 `autoPtr<AlgorithmBase>` ，其指向的是 `typeName = algorithmName` 的类的对象！**\n这样就实现了 `New` 函数作为 selector 的功能！\n\n所以，RTS 机制的本质可以总结如下：\n1. 基类里定义一个 `hashTable`，其 key 为类的 `typeName` ，value 为一个函数指针，这个函数指针指向的函数的返回值是基类类型的 `autoPtr` ，并且这个 `autoPtr` 指向类的一个临时对象（用 C++ 的 `new` 关键字创建 ）。这些在宏函数 `declareRunTimeSelectionTable` 中完成。\n\n2. 每创建一个派生类，都会调用一次 `addToRunTimeSelectionTable` 宏函数。这个宏函数会触发一次 `hashTable` 的更新操作。具体地说，宏函数的调用，会往基类里定义的 `hashTable` 插入一组值，这组值的 key 是该派生类的 `typeName` ，value 是一个函数，该函数返回的是指向派生类临时对象的指针。\n\n3. 类及其派生类编译成库，在编译过程中，会逐步往 `hashTable` 增加新元素，直到可选的模型全部添加到其中。\n\n4. 在需要调用这些类的地方，只需要定义基类的 `autoPtr`，并用基类中定义的 `New` 函数来初始化，即 `autoPtr<AlgorithmBase> algorithmPtr = AlgorithmBase::New(algorithmName);`。这样， `New` 函数就能根据调用的时候所提供的参数（即 `hashTable` 的 key），来从 `hashTable` 中选择对应的派生类（即 `hashTable` 的 value）。\n\n经过以上四步，就实现了 RTS 机制。\n\n参考：[source flux 的系列博文](http://www.sourceflux.de/blog/series/rts-2/)\n\n","source":"_posts/RTS1.md","raw":"title: \"OpenFOAM 中的 Run Time Selection 机制\"\ndate: 2016-03-12 13:06:48\ncomments: true\ntags:\n- Code Explained\n- RTS\ncategories:\n- OpenFOAM\n---\n\n[source flux 博客](http://www.sourceflux.de/blog/series/rts-2/) 曾经出过一个解释 Run Time Selection(RTS) 机制的系列博文，推荐想理解 RTS 的读者去仔细读读。本篇算是我在读完以后做的一个笔记，以及一些总结，供读者参考。\n\n<!--more-->\n\nOpenFOAM 中包含各个 CFD 相关的模块，每个模块，从 C++ 的角度来看，其实都是一个类的框架。基类用作接口，一个派生类则是一个具体的模型。OpenFOAM 中的模块广泛使用 RTS 机制，因此 OpenFOAM 的求解器中，只需要设定模型的调用接口。算例具体使用的是那个模型，则是在运行时才确定的，而且可以在算例运行过程中修改选中的模型。下面通过一个 [source flux 博客](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/) 提供的代码，来解读 RTS 机制的实现原理。\n为了方便解读，这里将代码摘录如下，代码所有权归 [source flux 博客](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/) 所有：\n\n```\n#include \"word.H\"\n#include \"messageStream.H\"\n#include \"argList.H\"\n\nusing namespace Foam;\n\n#include \"typeInfo.H\"\n#include \"runTimeSelectionTables.H\"\n#include \"addToRunTimeSelectionTable.H\"\n\n// Main program:\nclass AlgorithmBase\n{\n    public: \n\n        // Declare the static variable typeName of the class AlgorithmBase.\n        TypeName (\"base\");\n\n        // Empty constructor. \n        AlgorithmBase () {};\n\n        // Word constructor.\n        AlgorithmBase (const word& algorithmName) {};\n\n        // Destructor: needs to be declared virtual since \n        virtual ~AlgorithmBase() {};\n\n        // Macro for declaring stuff required for RTS \n        declareRunTimeSelectionTable\n        (\n            autoPtr, \n            AlgorithmBase, \n            Word, \n            (\n                const word& algorithmName\n            ),\n            (algorithmName)\n        )\n\n        // static Factory Method (selector)\n        static autoPtr<AlgorithmBase> New (const word& algorithmName)\n        {\n\n            // Find the Factory Method pointer in the RTS Table \n            // (HashTable<word, autoPtr<AlgorithmBase>(*)(word))\n            WordConstructorTable::iterator cstrIter =\n                WordConstructorTablePtr_->find(algorithmName);\n\n            // If the Factory Method was not found. \n            if (cstrIter == WordConstructorTablePtr_->end())\n            {\n                FatalErrorIn\n                (\n                    \"AlgorithmBase::New(const word&)\"\n                )   << \"Unknown AlgorithmBase type \"\n                    << algorithmName << nl << nl\n                    << \"Valid AlgorithmBase types are :\" << endl\n                    << WordConstructorTablePtr_->sortedToc()\n                    << exit(FatalError);\n            }\n\n            // Call the \"constructor\" and return the autoPtr<AlgorithmBase>\n            return cstrIter()(algorithmName);\n\n        }\n\n        // Make the class callable (function object) \n        virtual void operator()() \n        {\n            // Overridable default implementation\n            Info << \"AlgorithmBase::operator()()\" << endl;\n        }\n};\n\ndefineTypeNameAndDebug(AlgorithmBase, 0);\ndefineRunTimeSelectionTable(AlgorithmBase, Word);\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmBase, Word);\n\nclass AlgorithmNew\n:\n    public AlgorithmBase\n{\n    public: \n\n        // Declare the static variable typeName of the class AlgorithmNew.\n        TypeName (\"new\");\n\n        // Empty constructor. \n        AlgorithmNew () {};\n\n        // Word constructor.\n        AlgorithmNew (const word& algorithmName) {};\n\n        // Make the class callable (function object) \n        virtual void operator()()\n        {\n            Info << \"AlgorithmNew::operator()()\" << endl;\n        }\n};\n\ndefineTypeNameAndDebug(AlgorithmNew, 0);\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmNew , Word);\n\nclass AlgorithmAdditional\n:\n    public AlgorithmNew \n{\n    public: \n\n        // Declare the static variable typeName of the class AlgorithmAdditional.\n        TypeName (\"additional\");\n\n        // Empty constructor. \n        AlgorithmAdditional () {};\n\n        // Word constructor.\n        AlgorithmAdditional (const word& algorithmName) {};\n\n        // Make the class callable (function object) \n        virtual void operator()()\n        {\n            // Call base operator explicitly.\n            AlgorithmNew::operator()();\n            // Perform additional operations.\n            Info << \"AlgorithmAdditional::operator()()\" << endl;\n        }\n};\n\ndefineTypeNameAndDebug(AlgorithmAdditional, 0);\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmAdditional , Word);\n\nint main(int argc, char *argv[])\n{\n    argList::addOption\n    (\n        \"algorithmName\",\n        \"name of the run-time selected algorithm\"\n    );\n\n    argList args(argc, argv);\n\n    if (args.optionFound(\"algorithmName\"))\n    {\n        // Get the name of the algorithm from the arguments passed to the\n        // application. \n        const word algorithmName = args.option(\"algorithmName\");\n\n        // RTS call. \n        autoPtr<AlgorithmBase> algorithmPtr = AlgorithmBase::New(algorithmName);\n\n        // Get the reference to the algorithm from the smart pointer.\n        AlgorithmBase& algorithm = algorithmPtr(); \n\n        // Call the algorithm.\n        algorithm(); \n    }\n    else\n    {\n        FatalErrorIn\n        (\n            \"main()\"\n        )   << \"Please use with the 'algorithmName' option.\" << endl\n            << exit(FatalError);\n    }\n\n    Info<< \"\\nEnd\\n\" << endl;\n\n    return 0;\n}\n```\n\n在解读原理之前，先来看看这段代码。可以发现，RTS 机制的实现跟几个函数的调用有关： `declareRunTimeSelectionTable`， `defineRunTimeSelectionTable`， `defineTypeNameAndDebug`， `addToRunTimeSelectionTable`。规律可以总结如下：\n1. 基类类体里调用 `TypeName` 和 `declareRunTimeSelectionTable` 两个函数，类体外面调用 `defineTypeNameAndDebug` ， `defineRunTimeSelectionTable` 和 `addToRunTimeSelectionTable` 三个函数；\n2. 基类中需要一个静态 `New` 函数作为 `selector`。 \n3. 派生类类体中需要调用 `TypeName` 函数，类体外调用 `defineRunTimeSelectionTable` 和 `addToRunTimeSelectionTable` 两个宏函数。\n\n以上函数，经过搜索，发现都是定义在 `runTimeSelectionTables.H` 和 `addToRunTimeSelectionTable.H` 两个头文件中，而且，这些函数都是宏函数。\n\n看来，理解 RTS 的第一步就需要仔细看看这几个宏函数。\n\n先来看基类中的宏函数 `declareRunTimeSelectionTable` ，根据 [source flux 的博文](http://www.sourceflux.de/blog/run-time-type-selection-openfoam-selecting-types-based-type-name/)，这个宏函数针对前面的那段代码的展开结果为：\n\n```\n    typedef autoPtr< AlgorithmBase > (*WordConstructorPtr)( const word& algorithmName );\n   \n    typedef HashTable< WordConstructorPtr, word, string::hash > WordConstructorTable;\n    static WordConstructorTable* WordConstructorTablePtr_;\n    static void constructWordConstructorTables();\n    static void destroyWordConstructorTables();\n    template< class AlgorithmBaseType >\n    class addWordConstructorToTable\n    {\n        public:\n        static autoPtr< AlgorithmBase > New ( const word& algorithmName )\n        {\n            return autoPtr< AlgorithmBase >(new AlgorithmBaseType (algorithmName));\n        }\n        addWordConstructorToTable ( const word& lookup = AlgorithmBaseType::typeName )\n        {\n            constructWordConstructorTables();\n            if (!WordConstructorTablePtr_->insert(lookup, New))\n            {\n                std::cerr<< \"Duplicate entry \"\n                    << lookup << \" in runtime selection table \"\n                    << \"AlgorithmBase\" << std::endl;\n                error::safePrintStack(std::cerr);\n            }\n        }\n        \n        ~addWordConstructorToTable()\n        {\n            destroyWordConstructorTables();\n        }\n    };\n    template< class AlgorithmBaseType >\n    class addRemovableWordConstructorToTable\n    {\n        const word& lookup_;\n        \n        public:\n        static autoPtr< AlgorithmBase > New ( const word& algorithmName )\n        {\n            return autoPtr< AlgorithmBase >(new AlgorithmBaseType (algorithmName));\n        }\n        addRemovableWordConstructorToTable ( const word& lookup = AlgorithmBaseType::typeName )\n        : lookup_(lookup)\n        {\n            constructWordConstructorTables();\n            WordConstructorTablePtr_->set(lookup, New);\n        }\n       \n        ~addRemovableWordConstructorToTable()\n        {\n            if (WordConstructorTablePtr_)\n            {\n                WordConstructorTablePtr_->erase(lookup_);\n            }\n        }\n    };\n```\n注意，由于 `declareRunTimeSelectionTable` 是在基类类体里调用的，所以，以上内容都是在类体里的。这相当于在类体了定义了两个 `typedef`，一个静态数据成员，两个静态函数，还有两个类。\n先来看这两个 `typedef` 。第一个，定义的是一个函数指针，这样定义的结果是， `WordConstructorPtr` 代表一个指向参数为 `const word&`，返回类型为 `autoPtr< AlgorithmBase >` 的函数指针。第二个好理解，将一个 key 和 value 分别为 `word` 和 `WordConstructorPtr` 的 `hashTable` 定义了一个别名 `WordConstructorTable` 。\n静态数据成员 `WordConstructorTablePtr_` 是一个 `WordConstructorTable` 类型的指针。\n两个静态成员函数，这里只是声明了，并且注意到在下面定义的两个类中用到了这两个函数。\n\n继续看 `defineRunTimeSelectionTable(AlgorithmBase, Word) `。这个宏展开的结果为：\n```\nAlgorithmBase::WordConstructorTable* AlgorithmBase::WordConstructorTablePtr_ = __null;\n    void AlgorithmBase::constructWordConstructorTables() {\n        static bool constructed = false;\n        if (!constructed) {\n            constructed = true;\n            AlgorithmBase::WordConstructorTablePtr_ = new AlgorithmBase::WordConstructorTable;\n        }\n    };\n    \n    void AlgorithmBase::destroyWordConstructorTables() {\n        if (AlgorithmBase::WordConstructorTablePtr_) {\n            delete AlgorithmBase::WordConstructorTablePtr_;\n            AlgorithmBase::WordConstructorTablePtr_ = __null;\n        }\n    };\n```\n这个宏函数的主要功能，是对 `declareRunTimeSelectionTable` 中定义的静态数据成员和两个静态函数进行了定义。首先对静态数据成员 `WordConstructorTablePtr_` 初始化为 `__null`，然后 `constructWordConstructorTables` 函数将 `WordConstructorTablePtr_` 指向一个动态分配的 `WordConstructorTable` 。 `destroyWordConstructorTables` 则是对指针 `WordConstructorTablePtr_` 进行销毁。\n\n接着， `addToRunTimeSelectionTable(AlgorithmBase, AlgorithmBase, Word)` ，这宏函数展开以后其实就一句话：\n```\nAlgorithmBase::addWordConstructorToTable< AlgorithmBase > addAlgorithmBaseWordConstructorToAlgorithmBaseTable_;\n```\n这个语句，定义了一个 `addWordConstructorToTable` 的对象，仅此而已。但是，注意在创建一个类的对象的时候，是要调用该类的构造函数的。回头看 `addWordConstructorToTable` 类的构造函数，有意思的地方出现了。这个类的构造函数中，首先调用了 `constructWordConstructorTables` 函数，即对指针 `WordConstructorTablePtr_` 进行了初始化。然后，对 `WordConstructorTablePtr_` 进行 `insert` 操作，即，往其指向的 `hashTable` 插入 `key-value` 对。这里的 key 是创建对象 `addAlgorithmBaseWordConstructorToAlgorithmBaseTable_` 时代入的模板参数对应的 类的 `typeName`（这一句很长很绕，需要好好理解，因为很重要！），value 则是  `New` 函数。这个 `New` 函数，指的是定义在 `addWordConstructorToTable` 中的 `New` 函数。这个 `New` 函数非常重要，再写一遍：\n```\nstatic autoPtr< AlgorithmBase > New ( const word& algorithmName )\n{\n    return autoPtr< AlgorithmBase >(new AlgorithmBaseType (algorithmName));\n}\n```\n这个`New` 函数，返回的是一个 `AlgorithmBaseType`（这里是 AlgorithmBase ） 类型的临时对象的指针！对应这里的情形，现在可以知道这个 `insert` 操作将创建一个 “类的typeName -- 返回类的临时对象的引用的函数” 映射对，并增加到 `WordConstructorTablePtr_` 中（看来 `ddToRunTimeSelectionTable` 中创建一个 `addWordConstructorToTable` 类的对象，居然目的是为了调用其构造函数。）。如果 `insert` 操作失败（原因是想要插入的 key 与 `hashTable` 已有的重复了，所以每一个类都需要不同的 typeName！），就会报条目重复的错。\n\n好了，看完了基类相关的，在往下看派生类。前文已讲，派生类只需要在类体里调用 `TypeName`，然后在类体外调用 `addToRunTimeSelectionTable` 。对于派生类 `AlgorithmNew`，我们来看其具体的调用语句是\n```\naddToRunTimeSelectionTable(AlgorithmBase, AlgorithmNew , Word);\n```\n展开的结果应该是\n```\nAlgorithmBase::addWordConstructorToTable< AlgorithmNew > addAlgorithmNewWordConstructorToAlgorithmBaseTable_;\n```\n注意，这里又创建了一个 `addWordConstructorToTable` 类的对象，只是这里代入的模板参数是 `AlgorithmNew` 。于是，调用类的构造函数时代入的模板参数也就变了，所以这时 `New` 函数返回的将是 `AlgorithmNew` 类的临时对象的指针。并且， AlgorithmNew 这个名字与其对应的 `New` 函数组成的映射对，也被 `insert` 到 `WordConstructorTablePtr_` 里面。\n\n而 `AlgorithmAdditional` 这个类，虽然是继承自 `AlgorithmNew` ，但是也是间接继承 `AlgorithmBase` 。并且，在 `AlgorithmAdditional` 类的类体之后调用的宏函数 `addToRunTimeSelectionTable(AlgorithmBase, AlgorithmAdditional , Word)`  ，依然是将构建的映射对添加到了同一个 `hashTable` 里。\n\n最后，再来看一下 `selector`，即基类中定义的 `New` 函数。这个函数的返回值类型为 `autoPtr<AlgorithmBase>` ，参数为跟类的 `typeName` 一样，都是 `word&`。这个函数里面，首先定义了一个 `hashTable` 的迭代器 `cstrIter` ，利用迭代器来遍历搜索，看 `WordConstructorTable` 里面是否能找到参数 `algorithmName` 相符的 key 值，如果找不到，那就报错退出，并输出当前的 `WordConstructorTable` 中可选的项的名称（即 WordConstructorTablePtr_->sortedToc()）；如果找到了，那就返回这个 key 对应的 value。而 `WordConstructorTable` 的 value 是一个函数指针，所以 `cstrIter()` 返回的是 `algorithmName` 对应的那个 `New` 函数（不要跟基类 `AlgorithmBase` 中作为 selector 的 `New` 函数搞混了！）。进一步看， `cstrIter()(algorithmName)` 则表示的是函数调用了，传给函数的参数正是 `algorithmName`！\n**所以， `cstrIter()(algorithmName)` 返回的是 `autoPtr<AlgorithmBase>` ，其指向的是 `typeName = algorithmName` 的类的对象！**\n这样就实现了 `New` 函数作为 selector 的功能！\n\n所以，RTS 机制的本质可以总结如下：\n1. 基类里定义一个 `hashTable`，其 key 为类的 `typeName` ，value 为一个函数指针，这个函数指针指向的函数的返回值是基类类型的 `autoPtr` ，并且这个 `autoPtr` 指向类的一个临时对象（用 C++ 的 `new` 关键字创建 ）。这些在宏函数 `declareRunTimeSelectionTable` 中完成。\n\n2. 每创建一个派生类，都会调用一次 `addToRunTimeSelectionTable` 宏函数。这个宏函数会触发一次 `hashTable` 的更新操作。具体地说，宏函数的调用，会往基类里定义的 `hashTable` 插入一组值，这组值的 key 是该派生类的 `typeName` ，value 是一个函数，该函数返回的是指向派生类临时对象的指针。\n\n3. 类及其派生类编译成库，在编译过程中，会逐步往 `hashTable` 增加新元素，直到可选的模型全部添加到其中。\n\n4. 在需要调用这些类的地方，只需要定义基类的 `autoPtr`，并用基类中定义的 `New` 函数来初始化，即 `autoPtr<AlgorithmBase> algorithmPtr = AlgorithmBase::New(algorithmName);`。这样， `New` 函数就能根据调用的时候所提供的参数（即 `hashTable` 的 key），来从 `hashTable` 中选择对应的派生类（即 `hashTable` 的 value）。\n\n经过以上四步，就实现了 RTS 机制。\n\n参考：[source flux 的系列博文](http://www.sourceflux.de/blog/series/rts-2/)\n\n","slug":"RTS1","published":1,"updated":"2016-04-25T02:26:02.469Z","layout":"post","photos":[],"link":"","_id":"cioiqegf30043z8mbus88ehf2"},{"title":"涡结构提取","date":"2016-05-22T11:35:16.000Z","comments":1,"_content":"\n为了研究湍流的涡结构，需要有一些方法来将涡结构提取出来，比图在文章中常见类似这种图：\n![涡结构](/image/vortex/Lambda2.png)\n\n本篇介绍怎么在 OpenFOAM 中提取涡结构。\n\n<!--more-->\n\n历史上曾用过的涡结构提取有以下几种：\n1. 压强的局部极小值\n 在形成涡的地方，通常伴随着压强的极小值。比如:\n ![](/image/vortex/p.png)\n这种方法的缺点在于，缺乏客观的压力阈值来捕捉所有的涡结构，而且，压力出现极值的地方不见得就真的有涡。\n\n2. 流线\n 通过流线的封闭来显示涡的结构也是一种常见方法，比如\n ![](/image/vortex/s.png)\n 这种方法有一个最明显的缺点是，流线不满足伽利略不变性，即，如果换一个参考系，则可能显示出来的“涡结构”就完全不一样了。另外，这种方法也难以分辨两个很靠近的涡。\n\n3. 涡量的模\n 用涡量的模来显示涡结构是一种很常用的方法，类似这样\n ![](/image/vortex/magVor.png)\n 这种方法在自由剪切流中很有效，不过，对于壁面束缚流动则不太适用，原因是背景流动的剪切性导致的涡量模可以达到跟涡结构处的涡量的模差不多大小，这就使得涡结构难以从背景流动中分离出来了。并且，涡量的模的最大值通常发生在壁面上，而涡的核心显然不可能出现在壁面上。所以这种方法不适合用于提取边界层附近的涡结构。\n\n OpenFOAM 中提供了两种方法来提取涡结构：Q 和 Lambda2。\n\n+ 速度梯度张量的二阶不变量\n速度梯度 $\\nabla \\mathbf{U}$ 的二阶不变量 $Q$ 的定义为\n$$\nQ = \\frac{1}{2}\\Big ( ||\\mathbf{W}||^2 - ||\\mathbf{S}||^2 \\Big )\n$$\n其中\n$$\n\\mathbf{W} = \\frac{1}{2} \\Big ( \\nabla \\mathbf{U} - (\\nabla \\mathbf{U}) ^{\\mathrm{T}} \\Big ) \\\\\\\\\n||\\mathbf{W}|| = (\\mathbf{W}:\\mathbf{W})^{1/2} \\\\\\\\\n\\mathbf{S} = \\frac{1}{2} \\Big ( \\nabla \\mathbf{U} + (\\nabla \\mathbf{U}) ^{\\mathrm{T}} \\Big ) \\\\\\\\\n||\\mathbf{S}|| = (\\mathbf{S}:\\mathbf{S})^{1/2}\n$$\n\n可以用 $Q > 0$ 来作为涡结构存在的盘踞。\n在 OpenFOAM 中，有一个程序用来计算 $Q$，名字就叫 `Q`。在流场计算完毕以后，可以运行 `Q`，然后在 paraview 中显示 `Q` 值大于 0 的等值面来显示涡的结构。只是，OpenFOAM 中 $Q$ 的计算用的是另一种方法：\n\n```\n//Q.C \nvolTensorField gradU(fvc::grad(U));\n\nvolScalarField Q\n(\n    IOobject\n    (\n        \"Q\",\n        runTime.timeName(),\n        mesh,\n        IOobject::NO_READ,\n        IOobject::NO_WRITE\n    ),\n    0.5*(sqr(tr(gradU)) - tr(((gradU)&(gradU))))\n);\n```\n代码里注释说这是另一种计算 $Q$ 的方法，与上面公式的计算方法差别很小。\n\n+ 张量 $\\mathbf{W} \\cdot \\mathbf{W} + \\mathbf{S} \\cdot \\mathbf{S}$ 的第二大特征值\n\n另一种判据是 $\\mathbf{W} \\cdot \\mathbf{W} + \\mathbf{S} \\cdot \\mathbf{S}$ 的第二大特征值 $\\lambda \\_ 2 < 0$。\n在 OpenFOAM 中有一个程序用来计算 $\\lambda \\_ 2$ ：`Lambda2`。\n```\n//Lambda2.C\n const volTensorField gradU(fvc::grad(U));\n\n        volTensorField SSplusWW\n        (\n            (symm(gradU) & symm(gradU)) + (skew(gradU) & skew(gradU))\n        );\n\n        volScalarField Lambda2\n        (\n            IOobject\n            (\n                \"Lambda2\",\n                runTime.timeName(),\n                mesh,\n                IOobject::NO_READ,\n                IOobject::NO_WRITE\n            ),\n            -eigenValues(SSplusWW)().component(vector::Y)\n        );\n\n        Info<< \"    Writing -Lambda2\" << endl;\n        Lambda2.write();\n```\n注意，OpenFOAM 返回的是 $\\- \\lambda \\_ 2$，所以，在计算了 `Lambda2` 后，需要通过 `Lambda2` 大于 0 的等值面来显示涡结构。本篇开头第一张图片，显示的是圆柱绕流的 `Lambda2 = 500` 等值面。\n\n**参考** \nEugene de Villiers, The Potential of Large Eddy Simulation for the Modeling of Wall Bounded Flows, Ph.D Thesis, Imperial College of Science, 2005.\n","source":"_posts/QAndLambda.md","raw":"title: \"涡结构提取\"\ndate: 2016-05-22 19:35:16\ncomments: true\ntags:\n- OpenFOAM\n- Postprocessing\ncategories:\n- OpenFOAM\n---\n\n为了研究湍流的涡结构，需要有一些方法来将涡结构提取出来，比图在文章中常见类似这种图：\n![涡结构](/image/vortex/Lambda2.png)\n\n本篇介绍怎么在 OpenFOAM 中提取涡结构。\n\n<!--more-->\n\n历史上曾用过的涡结构提取有以下几种：\n1. 压强的局部极小值\n 在形成涡的地方，通常伴随着压强的极小值。比如:\n ![](/image/vortex/p.png)\n这种方法的缺点在于，缺乏客观的压力阈值来捕捉所有的涡结构，而且，压力出现极值的地方不见得就真的有涡。\n\n2. 流线\n 通过流线的封闭来显示涡的结构也是一种常见方法，比如\n ![](/image/vortex/s.png)\n 这种方法有一个最明显的缺点是，流线不满足伽利略不变性，即，如果换一个参考系，则可能显示出来的“涡结构”就完全不一样了。另外，这种方法也难以分辨两个很靠近的涡。\n\n3. 涡量的模\n 用涡量的模来显示涡结构是一种很常用的方法，类似这样\n ![](/image/vortex/magVor.png)\n 这种方法在自由剪切流中很有效，不过，对于壁面束缚流动则不太适用，原因是背景流动的剪切性导致的涡量模可以达到跟涡结构处的涡量的模差不多大小，这就使得涡结构难以从背景流动中分离出来了。并且，涡量的模的最大值通常发生在壁面上，而涡的核心显然不可能出现在壁面上。所以这种方法不适合用于提取边界层附近的涡结构。\n\n OpenFOAM 中提供了两种方法来提取涡结构：Q 和 Lambda2。\n\n+ 速度梯度张量的二阶不变量\n速度梯度 $\\nabla \\mathbf{U}$ 的二阶不变量 $Q$ 的定义为\n$$\nQ = \\frac{1}{2}\\Big ( ||\\mathbf{W}||^2 - ||\\mathbf{S}||^2 \\Big )\n$$\n其中\n$$\n\\mathbf{W} = \\frac{1}{2} \\Big ( \\nabla \\mathbf{U} - (\\nabla \\mathbf{U}) ^{\\mathrm{T}} \\Big ) \\\\\\\\\n||\\mathbf{W}|| = (\\mathbf{W}:\\mathbf{W})^{1/2} \\\\\\\\\n\\mathbf{S} = \\frac{1}{2} \\Big ( \\nabla \\mathbf{U} + (\\nabla \\mathbf{U}) ^{\\mathrm{T}} \\Big ) \\\\\\\\\n||\\mathbf{S}|| = (\\mathbf{S}:\\mathbf{S})^{1/2}\n$$\n\n可以用 $Q > 0$ 来作为涡结构存在的盘踞。\n在 OpenFOAM 中，有一个程序用来计算 $Q$，名字就叫 `Q`。在流场计算完毕以后，可以运行 `Q`，然后在 paraview 中显示 `Q` 值大于 0 的等值面来显示涡的结构。只是，OpenFOAM 中 $Q$ 的计算用的是另一种方法：\n\n```\n//Q.C \nvolTensorField gradU(fvc::grad(U));\n\nvolScalarField Q\n(\n    IOobject\n    (\n        \"Q\",\n        runTime.timeName(),\n        mesh,\n        IOobject::NO_READ,\n        IOobject::NO_WRITE\n    ),\n    0.5*(sqr(tr(gradU)) - tr(((gradU)&(gradU))))\n);\n```\n代码里注释说这是另一种计算 $Q$ 的方法，与上面公式的计算方法差别很小。\n\n+ 张量 $\\mathbf{W} \\cdot \\mathbf{W} + \\mathbf{S} \\cdot \\mathbf{S}$ 的第二大特征值\n\n另一种判据是 $\\mathbf{W} \\cdot \\mathbf{W} + \\mathbf{S} \\cdot \\mathbf{S}$ 的第二大特征值 $\\lambda \\_ 2 < 0$。\n在 OpenFOAM 中有一个程序用来计算 $\\lambda \\_ 2$ ：`Lambda2`。\n```\n//Lambda2.C\n const volTensorField gradU(fvc::grad(U));\n\n        volTensorField SSplusWW\n        (\n            (symm(gradU) & symm(gradU)) + (skew(gradU) & skew(gradU))\n        );\n\n        volScalarField Lambda2\n        (\n            IOobject\n            (\n                \"Lambda2\",\n                runTime.timeName(),\n                mesh,\n                IOobject::NO_READ,\n                IOobject::NO_WRITE\n            ),\n            -eigenValues(SSplusWW)().component(vector::Y)\n        );\n\n        Info<< \"    Writing -Lambda2\" << endl;\n        Lambda2.write();\n```\n注意，OpenFOAM 返回的是 $\\- \\lambda \\_ 2$，所以，在计算了 `Lambda2` 后，需要通过 `Lambda2` 大于 0 的等值面来显示涡结构。本篇开头第一张图片，显示的是圆柱绕流的 `Lambda2 = 500` 等值面。\n\n**参考** \nEugene de Villiers, The Potential of Large Eddy Simulation for the Modeling of Wall Bounded Flows, Ph.D Thesis, Imperial College of Science, 2005.\n","slug":"QAndLambda","published":1,"updated":"2016-05-22T14:31:52.155Z","layout":"post","photos":[],"link":"","_id":"cioiqegf60047z8mbwxcpxfzu"},{"title":"OpenFOAM 中的 div 与 snGrad 操作符","date":"2015-05-17T06:02:37.000Z","comments":1,"_content":"OpenFOAM 的方便之处之一是利用`C++`的类模板和函数重载等技术定义了很多各种离散操作符，如`div`,`laplacian`,`grad` 等等。利用这些操作符，很容易就能对偏微分方程进行离散，并构建起线性方程组。但是，这些操作符真正执行的运算，却需要结合有限体积方法的本质来理解一番才能真正掌握。下面尝试着对 OpenFOAM 中的 `div` 和 `snGrad` 操作符进行一点解读。\n\n<!--more-->\n\n### 1. div 操作符的本质\n\n `div`操作符表面看，是计算散度的，实际上，在OpenFAOM中，div 操作符的作用是**加和**，比如说 $ \\nabla \\cdot (UU)$，在OpenFOAM中表示为`fvm::div(phi,U)`，这段代码真正执行的是$\\sum\\_f U\\_f \\phi\\_f$运算，即将每个网格包含的面上的流率与速度乘积，然后再加起来。再比如，`twoPhaseEulerFoam`的`UaEqn`方程有一项是`fvm::sp(fvc::div(phia),Ua)`，其对应的公式是 $U\\_a(\\nabla \\cdot U\\_a)$。为什么是这样呢？以下试图对背后的原理做一点解释：\n单相流的动量守恒方程的微分形式如下：\n$$\\frac{\\partial U}{\\partial t}+\\nabla\\cdot (UU)+\\nabla \\cdot dev(-\\nu\\_{eff} (\\nabla U+(\\nabla U)^T))=-\\nabla p +Q$$\n为了使用有限体积方法，需要将动量方程写成积分形式，即\n$$\\int\\_V\\left [\\frac{\\partial U}{\\partial t}+\\nabla\\cdot (UU)+\\nabla \\cdot dev(-\\nu\\_{eff} (\\nabla U+(\\nabla U)^T))\\right ]dV=\\int\\_V\\left [ -\\nabla p +Q \\right ]dV$$\n这里只分析$\\int\\_V \\nabla\\cdot (UU) dV$这一项，利用高斯定理，可以将这一个体积分，转化成对包围该体积微元的表面的面积分:$\\oint\\_{\\partial V} (UU)\\cdot dS$，其中 $dS$ 是面积微元矢量。又因为实际操作中，一个体积微元总是由有限的几个面组成的，所以$$\\oint\\_{\\partial V} (UU)\\cdot dS=\\sum\\_f\\int\\_{S\\_f}(UU)\\cdot dS\\_f=\\sum\\_f(UU)\\_f\\cdot S\\_f$$，其中$$(UU)\\_f=\\frac{1}{mag(S\\_f)}\\int\\_{S\\_f}(UU)\\cdot dS\\_f$$。\n这里做一个近似：$$(UU)\\_f\\approx(U\\_fU\\_f)$$\n于是得到$$\\sum\\_f(UU)\\_f\\cdot S\\_f\\approx \\sum\\_f(U\\_fU\\_f)\\cdot S\\_f$$\n运用张量计算规则$[\\mathbf{uv}\\cdot\\mathbf{w}]=\\mathbf{u}(\\mathbf{v} \\cdot \\mathbf{w})$，得到\n$$\\sum\\_f(U\\_fU\\_f)\\cdot S\\_f=\\sum\\_fU\\_f(U\\_f\\cdot S\\_f)=\\sum\\_fU\\_f\\phi\\_f$$\n综上，OpenFOAM中的代码`fvm::div(UU)`对应的公式其实是$\\int\\_V \\nabla\\cdot (UU) dV$，而根据推导，在有限体积方法中 $\\int\\_V \\nabla\\cdot (UU) dV=\\sum\\_f(U\\_f\\phi\\_f)$，所以，`fvm::div(UU)`实质上**进行的运算是，把网格当作体积微元，将网格中心的速度 $U$ 插值到包围该网格的所有面上的面心上得到 $U\\_f$，并计算每个面上的速度通量 $\\phi\\_f$，然后返回每一个面上的速度与通量乘积的加和**。$U\\_f$的计算需要用到本网格与邻近网格的速度值，离散格式的作用就体现在如果利用本网格与邻近网格的速度得到面上的速度。\n\n 再来看上面提到的另一项$U\\_a(\\nabla \\cdot U\\_a)$，根据上面类似的推导\n$$\\int\\_V\\left [ U\\_a(\\nabla \\cdot U\\_a)\\right ]dV=U\\_a\\int\\_V(\\nabla \\cdot U\\_a)dV$$\n注意，这里之所以能这样变换，是因为在一个体积微元dV内，Ua是常数。根据高斯定理\n$$\\int\\_V(\\nabla \\cdot U\\_a)dV=\\oint\\_{\\partial V}U\\_a\\cdot dS=\\sum\\_f(U\\_a)\\_f\\cdot S\\_f=\\sum\\_f(\\phi\\_a)\\_f$$\n于是得到\n$$\\int\\_V\\left [ U\\_a(\\nabla \\cdot U\\_a)\\right ]dV=U\\_a\\sum\\_f(\\phi\\_a)\\_f$$\n这一项在OpenFOAM中的表达是`fvm::sp(fvc::div(phia),Ua)`，含义就很明显了,这一项相当于是一个系数与需要求解的量$U\\_a$的乘积，所以被当作隐式的源项来处里。注意我先前以为把$(\\nabla \\cdot U\\_a)$当作显式处理是人为简化的结果，其实不然，这是自然而然的结果。而在这里也可以看出，$\\sum\\_f(\\phi\\_a)\\_f$对应的代码是`fvc::div(phia)`，也印证了上面的观点，即`div`操作符**本质上是在作加和运算**。\n\n\n### 2. grad 与 snGrad\n\n在`twoPhaseEulerFoam`中，$\\frac{\\nabla \\alpha}{\\alpha}$在两个不同的地方用了两种不同的表示。\n$$\n\\nabla \\cdot \\left \\\\{ [\\nu\\_{eff,a} \\frac{\\nabla \\alpha}{\\alpha}][U\\_a] \\right \\\\} \n$$\n对应的代码是：`fvm::div(phiRa,Ua)`，其中\n```\nphiRa=-fvc::interpolate(nuEffa)*mesh.magSf()*fvc::snGrad(alpha)\n     /fvc::interpolate(alpha + scalar(0.001));\n```\n而另一项\n$$[\\frac{\\nabla \\alpha }{\\alpha}]\\cdot\\{Rca\\}$$\n对应的代码是`fvc::grad(alpha)/fvc::average(alpha + scalar(0.001)) & Rca`\n\n **下面是我对这个的理解**：\n对于\n$$\n\\nabla \\cdot \\left \\\\{ [\\nu\\_{eff,a} \\frac{\\nabla \\alpha}{\\alpha}][U\\_a] \\right \\\\} \n$$\n\n其处理方法跟$\\nabla \\cdot(U\\_aU\\_a)$是一样的，因为$ \\frac{\\nabla \\alpha}{\\alpha}$也是一个矢量。`phiRa`相当于是 $\\left [\\nu\\_{eff,a} \\frac{\\nabla \\alpha}{\\alpha} \\right ]$这个矢量的界面通量，类比于`phia`。`phia`的定义是`linearInterpolate(Ua) & mesh.Sf()`，而`phiRa`理论上应该也可以定义成类似于`linearInterpolate(gradalpha) & mesh.Sf()`，前提是要先定义一个 `volVectorField gradalpha=-nuEffa*fvc::grad(alpha)/alpha`。但是考虑到界面通量本质上是先将一个`volVectorField`插值到面上，并乘以面积矢量，对于 $\\frac{\\nabla \\alpha}{\\alpha}$，完全可以直接求出每个面上的$\\frac{\\nabla \\alpha}{\\alpha}$，然后乘以面积矢量，而不需要先建立体中心的$\\frac{\\nabla \\alpha}{\\alpha}$再插值到面上。`snGrad`就是这样一个用来求面上的梯度量的函数，它求解面上的梯度时，采用如下公式：\n$$(\\nabla \\phi)\\_f=\\frac{\\phi\\_N-\\phi\\_P}{|\\mathbf{d}|}$$\n即用相邻网格的值减去面所属网格的值，再除以两个网格中心矢量的模。注意这个处理对于正交网格是精确的，对于非正交网格，只是一个近似处理。而且要注意，这样求得的面上的梯度值已经是一个标量了。再回到`phiRa`的定义，既然`fvc::snGrad(alpha)`已经是标量了，那只要再乘以面积矢量的模`mesh.magSf()`，便是界面上的通量了。`fvc::interpolate(nuEffa)`和`fvc::interpolate(alpha + scalar(0.001))`则分别是将volField插值到面。\n\n 再来看$$[\\frac{\\nabla \\alpha }{\\alpha}]\\cdot\\{Rca\\}$$\n 这一项是被当作显式的源项来处理，所以，我们需要得到的是一个volVcetorField。所以这里将$\\nabla \\alpha$处理成 volVectorField，再与作为 volTensorField 的 `Rca` 进行点乘，得到的就是 volVcetorField。因此，这里的$\\frac{\\nabla \\alpha }{\\alpha}$对应的代码是 `fvc::grad(alpha)/fvc::average(alpha + scalar(0.001))`。注意这里的 `fvc::average()` 函数的定义[如下](http://foam.sourceforge.net/docs/cpp/a05323_source.html#l00046)：\n```\n   43 template<class Type>\n   44 tmp<GeometricField<Type, fvPatchField, volMesh> >\n   45 average\n   46 (\n   47     const GeometricField<Type, fvsPatchField, surfaceMesh>& ssf\n   48 )\n   49 {\n   50     const fvMesh& mesh = ssf.mesh();\n   51 \n   52     tmp<GeometricField<Type, fvPatchField, volMesh> > taverage\n   53     (\n   54         new GeometricField<Type, fvPatchField, volMesh>\n   55         (\n   56             IOobject\n   57             (\n   58                 \"average(\"+ssf.name()+')',\n   59                 ssf.instance(),\n   60                 mesh,\n   61                 IOobject::NO_READ,\n   62                 IOobject::NO_WRITE\n   63             ),\n   64             mesh,\n   65             ssf.dimensions()\n   66         )\n   67     );\n   68 \n   69     GeometricField<Type, fvPatchField, volMesh>& av = taverage();\n   70 \n   71     av.internalField() =\n   72     (\n   73         surfaceSum(mesh.magSf()*ssf)/surfaceSum(mesh.magSf())\n   74     )().internalField();\n   75 \n   76     typename GeometricField<Type, fvPatchField, volMesh>::\n   77     GeometricBoundaryField& bav = av.boundaryField();\n   78 \n   79     forAll(bav, patchi)\n   80     {\n   81         bav[patchi] = ssf.boundaryField()[patchi];\n   82     }\n   83 \n   84     av.correctBoundaryConditions();\n   85 \n   86     return taverage;\n   87 }\n   88 \n   89 \n   90 template<class Type>\n   91 tmp<GeometricField<Type, fvPatchField, volMesh> >\n   92 average\n   93 (\n   94     const tmp<GeometricField<Type, fvsPatchField, surfaceMesh> >& tssf\n   95 )\n   96 {\n   97     tmp<GeometricField<Type, fvPatchField, volMesh> > taverage\n   98     (\n   99         fvc::average(tssf())\n  100     );\n  101     tssf.clear();\n  102     return taverage;\n  103 }\n  104 \n  105 \n  106 template<class Type>\n  107 tmp<GeometricField<Type, fvPatchField, volMesh> >\n  108 average\n  109 (\n  110     const GeometricField<Type, fvPatchField, volMesh>& vtf\n  111 )\n  112 {\n  113     return fvc::average(linearInterpolate(vtf));\n  114 }\n  115 \n  116 \n  117 template<class Type>\n  118 tmp<GeometricField<Type, fvPatchField, volMesh> >\n  119 average\n  120 (\n  121     const tmp<GeometricField<Type, fvPatchField, volMesh> >& tvtf\n  122 )\n  123 {\n  124     tmp<GeometricField<Type, fvPatchField, volMesh> > taverage\n  125     (\n  126         fvc::average(tvtf())\n  127     );\n  128     tvtf.clear();\n  129     return taverage;\n  130 }\n```\n可以看出，`fvc::average()`函数的返回类型是`tmp<GeometricField<Type, fvPatchField, volMesh> >`，对应`alpha`，返回类型就是`tmp<GeometricField<scalar, fvPatchField, volMesh> >`，即`tmp<volScalarField>`。`average`函数的核心定义见代码71-74行，可见`average`采用的是面积加权平均，即$$\\overline{\\phi}=\\frac{\\sum\\_f\\phi\\_f*|S\\_f|}{\\sum\\_f |S\\_f|}$$。如果给`average`的参数类型是surfaceFiled，那么直接计算平均值后返回volField，如果给的参数的volField，那就先将volField插值到面，计算平均值后再返回volField，见代码106-114行。\n\n### 参考资料\n  1. http://www.openfoam.org/docs/cpp/\n  2. OpenFOAM: A little User-Manual, Gerhard Holzinger, https://github.com/ParticulateFlow/OSCCAR-doc\n","source":"_posts/OpenFOAMcode1.md","raw":"title: \"OpenFOAM 中的 div 与 snGrad 操作符\"\ndate: 2015-05-17 14:02:37\ncomments: true\ntags:\n  - OpenFOAM\n  - Code Explained\ncategories:\n - OpenFOAM\n---\nOpenFOAM 的方便之处之一是利用`C++`的类模板和函数重载等技术定义了很多各种离散操作符，如`div`,`laplacian`,`grad` 等等。利用这些操作符，很容易就能对偏微分方程进行离散，并构建起线性方程组。但是，这些操作符真正执行的运算，却需要结合有限体积方法的本质来理解一番才能真正掌握。下面尝试着对 OpenFOAM 中的 `div` 和 `snGrad` 操作符进行一点解读。\n\n<!--more-->\n\n### 1. div 操作符的本质\n\n `div`操作符表面看，是计算散度的，实际上，在OpenFAOM中，div 操作符的作用是**加和**，比如说 $ \\nabla \\cdot (UU)$，在OpenFOAM中表示为`fvm::div(phi,U)`，这段代码真正执行的是$\\sum\\_f U\\_f \\phi\\_f$运算，即将每个网格包含的面上的流率与速度乘积，然后再加起来。再比如，`twoPhaseEulerFoam`的`UaEqn`方程有一项是`fvm::sp(fvc::div(phia),Ua)`，其对应的公式是 $U\\_a(\\nabla \\cdot U\\_a)$。为什么是这样呢？以下试图对背后的原理做一点解释：\n单相流的动量守恒方程的微分形式如下：\n$$\\frac{\\partial U}{\\partial t}+\\nabla\\cdot (UU)+\\nabla \\cdot dev(-\\nu\\_{eff} (\\nabla U+(\\nabla U)^T))=-\\nabla p +Q$$\n为了使用有限体积方法，需要将动量方程写成积分形式，即\n$$\\int\\_V\\left [\\frac{\\partial U}{\\partial t}+\\nabla\\cdot (UU)+\\nabla \\cdot dev(-\\nu\\_{eff} (\\nabla U+(\\nabla U)^T))\\right ]dV=\\int\\_V\\left [ -\\nabla p +Q \\right ]dV$$\n这里只分析$\\int\\_V \\nabla\\cdot (UU) dV$这一项，利用高斯定理，可以将这一个体积分，转化成对包围该体积微元的表面的面积分:$\\oint\\_{\\partial V} (UU)\\cdot dS$，其中 $dS$ 是面积微元矢量。又因为实际操作中，一个体积微元总是由有限的几个面组成的，所以$$\\oint\\_{\\partial V} (UU)\\cdot dS=\\sum\\_f\\int\\_{S\\_f}(UU)\\cdot dS\\_f=\\sum\\_f(UU)\\_f\\cdot S\\_f$$，其中$$(UU)\\_f=\\frac{1}{mag(S\\_f)}\\int\\_{S\\_f}(UU)\\cdot dS\\_f$$。\n这里做一个近似：$$(UU)\\_f\\approx(U\\_fU\\_f)$$\n于是得到$$\\sum\\_f(UU)\\_f\\cdot S\\_f\\approx \\sum\\_f(U\\_fU\\_f)\\cdot S\\_f$$\n运用张量计算规则$[\\mathbf{uv}\\cdot\\mathbf{w}]=\\mathbf{u}(\\mathbf{v} \\cdot \\mathbf{w})$，得到\n$$\\sum\\_f(U\\_fU\\_f)\\cdot S\\_f=\\sum\\_fU\\_f(U\\_f\\cdot S\\_f)=\\sum\\_fU\\_f\\phi\\_f$$\n综上，OpenFOAM中的代码`fvm::div(UU)`对应的公式其实是$\\int\\_V \\nabla\\cdot (UU) dV$，而根据推导，在有限体积方法中 $\\int\\_V \\nabla\\cdot (UU) dV=\\sum\\_f(U\\_f\\phi\\_f)$，所以，`fvm::div(UU)`实质上**进行的运算是，把网格当作体积微元，将网格中心的速度 $U$ 插值到包围该网格的所有面上的面心上得到 $U\\_f$，并计算每个面上的速度通量 $\\phi\\_f$，然后返回每一个面上的速度与通量乘积的加和**。$U\\_f$的计算需要用到本网格与邻近网格的速度值，离散格式的作用就体现在如果利用本网格与邻近网格的速度得到面上的速度。\n\n 再来看上面提到的另一项$U\\_a(\\nabla \\cdot U\\_a)$，根据上面类似的推导\n$$\\int\\_V\\left [ U\\_a(\\nabla \\cdot U\\_a)\\right ]dV=U\\_a\\int\\_V(\\nabla \\cdot U\\_a)dV$$\n注意，这里之所以能这样变换，是因为在一个体积微元dV内，Ua是常数。根据高斯定理\n$$\\int\\_V(\\nabla \\cdot U\\_a)dV=\\oint\\_{\\partial V}U\\_a\\cdot dS=\\sum\\_f(U\\_a)\\_f\\cdot S\\_f=\\sum\\_f(\\phi\\_a)\\_f$$\n于是得到\n$$\\int\\_V\\left [ U\\_a(\\nabla \\cdot U\\_a)\\right ]dV=U\\_a\\sum\\_f(\\phi\\_a)\\_f$$\n这一项在OpenFOAM中的表达是`fvm::sp(fvc::div(phia),Ua)`，含义就很明显了,这一项相当于是一个系数与需要求解的量$U\\_a$的乘积，所以被当作隐式的源项来处里。注意我先前以为把$(\\nabla \\cdot U\\_a)$当作显式处理是人为简化的结果，其实不然，这是自然而然的结果。而在这里也可以看出，$\\sum\\_f(\\phi\\_a)\\_f$对应的代码是`fvc::div(phia)`，也印证了上面的观点，即`div`操作符**本质上是在作加和运算**。\n\n\n### 2. grad 与 snGrad\n\n在`twoPhaseEulerFoam`中，$\\frac{\\nabla \\alpha}{\\alpha}$在两个不同的地方用了两种不同的表示。\n$$\n\\nabla \\cdot \\left \\\\{ [\\nu\\_{eff,a} \\frac{\\nabla \\alpha}{\\alpha}][U\\_a] \\right \\\\} \n$$\n对应的代码是：`fvm::div(phiRa,Ua)`，其中\n```\nphiRa=-fvc::interpolate(nuEffa)*mesh.magSf()*fvc::snGrad(alpha)\n     /fvc::interpolate(alpha + scalar(0.001));\n```\n而另一项\n$$[\\frac{\\nabla \\alpha }{\\alpha}]\\cdot\\{Rca\\}$$\n对应的代码是`fvc::grad(alpha)/fvc::average(alpha + scalar(0.001)) & Rca`\n\n **下面是我对这个的理解**：\n对于\n$$\n\\nabla \\cdot \\left \\\\{ [\\nu\\_{eff,a} \\frac{\\nabla \\alpha}{\\alpha}][U\\_a] \\right \\\\} \n$$\n\n其处理方法跟$\\nabla \\cdot(U\\_aU\\_a)$是一样的，因为$ \\frac{\\nabla \\alpha}{\\alpha}$也是一个矢量。`phiRa`相当于是 $\\left [\\nu\\_{eff,a} \\frac{\\nabla \\alpha}{\\alpha} \\right ]$这个矢量的界面通量，类比于`phia`。`phia`的定义是`linearInterpolate(Ua) & mesh.Sf()`，而`phiRa`理论上应该也可以定义成类似于`linearInterpolate(gradalpha) & mesh.Sf()`，前提是要先定义一个 `volVectorField gradalpha=-nuEffa*fvc::grad(alpha)/alpha`。但是考虑到界面通量本质上是先将一个`volVectorField`插值到面上，并乘以面积矢量，对于 $\\frac{\\nabla \\alpha}{\\alpha}$，完全可以直接求出每个面上的$\\frac{\\nabla \\alpha}{\\alpha}$，然后乘以面积矢量，而不需要先建立体中心的$\\frac{\\nabla \\alpha}{\\alpha}$再插值到面上。`snGrad`就是这样一个用来求面上的梯度量的函数，它求解面上的梯度时，采用如下公式：\n$$(\\nabla \\phi)\\_f=\\frac{\\phi\\_N-\\phi\\_P}{|\\mathbf{d}|}$$\n即用相邻网格的值减去面所属网格的值，再除以两个网格中心矢量的模。注意这个处理对于正交网格是精确的，对于非正交网格，只是一个近似处理。而且要注意，这样求得的面上的梯度值已经是一个标量了。再回到`phiRa`的定义，既然`fvc::snGrad(alpha)`已经是标量了，那只要再乘以面积矢量的模`mesh.magSf()`，便是界面上的通量了。`fvc::interpolate(nuEffa)`和`fvc::interpolate(alpha + scalar(0.001))`则分别是将volField插值到面。\n\n 再来看$$[\\frac{\\nabla \\alpha }{\\alpha}]\\cdot\\{Rca\\}$$\n 这一项是被当作显式的源项来处理，所以，我们需要得到的是一个volVcetorField。所以这里将$\\nabla \\alpha$处理成 volVectorField，再与作为 volTensorField 的 `Rca` 进行点乘，得到的就是 volVcetorField。因此，这里的$\\frac{\\nabla \\alpha }{\\alpha}$对应的代码是 `fvc::grad(alpha)/fvc::average(alpha + scalar(0.001))`。注意这里的 `fvc::average()` 函数的定义[如下](http://foam.sourceforge.net/docs/cpp/a05323_source.html#l00046)：\n```\n   43 template<class Type>\n   44 tmp<GeometricField<Type, fvPatchField, volMesh> >\n   45 average\n   46 (\n   47     const GeometricField<Type, fvsPatchField, surfaceMesh>& ssf\n   48 )\n   49 {\n   50     const fvMesh& mesh = ssf.mesh();\n   51 \n   52     tmp<GeometricField<Type, fvPatchField, volMesh> > taverage\n   53     (\n   54         new GeometricField<Type, fvPatchField, volMesh>\n   55         (\n   56             IOobject\n   57             (\n   58                 \"average(\"+ssf.name()+')',\n   59                 ssf.instance(),\n   60                 mesh,\n   61                 IOobject::NO_READ,\n   62                 IOobject::NO_WRITE\n   63             ),\n   64             mesh,\n   65             ssf.dimensions()\n   66         )\n   67     );\n   68 \n   69     GeometricField<Type, fvPatchField, volMesh>& av = taverage();\n   70 \n   71     av.internalField() =\n   72     (\n   73         surfaceSum(mesh.magSf()*ssf)/surfaceSum(mesh.magSf())\n   74     )().internalField();\n   75 \n   76     typename GeometricField<Type, fvPatchField, volMesh>::\n   77     GeometricBoundaryField& bav = av.boundaryField();\n   78 \n   79     forAll(bav, patchi)\n   80     {\n   81         bav[patchi] = ssf.boundaryField()[patchi];\n   82     }\n   83 \n   84     av.correctBoundaryConditions();\n   85 \n   86     return taverage;\n   87 }\n   88 \n   89 \n   90 template<class Type>\n   91 tmp<GeometricField<Type, fvPatchField, volMesh> >\n   92 average\n   93 (\n   94     const tmp<GeometricField<Type, fvsPatchField, surfaceMesh> >& tssf\n   95 )\n   96 {\n   97     tmp<GeometricField<Type, fvPatchField, volMesh> > taverage\n   98     (\n   99         fvc::average(tssf())\n  100     );\n  101     tssf.clear();\n  102     return taverage;\n  103 }\n  104 \n  105 \n  106 template<class Type>\n  107 tmp<GeometricField<Type, fvPatchField, volMesh> >\n  108 average\n  109 (\n  110     const GeometricField<Type, fvPatchField, volMesh>& vtf\n  111 )\n  112 {\n  113     return fvc::average(linearInterpolate(vtf));\n  114 }\n  115 \n  116 \n  117 template<class Type>\n  118 tmp<GeometricField<Type, fvPatchField, volMesh> >\n  119 average\n  120 (\n  121     const tmp<GeometricField<Type, fvPatchField, volMesh> >& tvtf\n  122 )\n  123 {\n  124     tmp<GeometricField<Type, fvPatchField, volMesh> > taverage\n  125     (\n  126         fvc::average(tvtf())\n  127     );\n  128     tvtf.clear();\n  129     return taverage;\n  130 }\n```\n可以看出，`fvc::average()`函数的返回类型是`tmp<GeometricField<Type, fvPatchField, volMesh> >`，对应`alpha`，返回类型就是`tmp<GeometricField<scalar, fvPatchField, volMesh> >`，即`tmp<volScalarField>`。`average`函数的核心定义见代码71-74行，可见`average`采用的是面积加权平均，即$$\\overline{\\phi}=\\frac{\\sum\\_f\\phi\\_f*|S\\_f|}{\\sum\\_f |S\\_f|}$$。如果给`average`的参数类型是surfaceFiled，那么直接计算平均值后返回volField，如果给的参数的volField，那就先将volField插值到面，计算平均值后再返回volField，见代码106-114行。\n\n### 参考资料\n  1. http://www.openfoam.org/docs/cpp/\n  2. OpenFOAM: A little User-Manual, Gerhard Holzinger, https://github.com/ParticulateFlow/OSCCAR-doc\n","slug":"OpenFOAMcode1","published":1,"updated":"2016-06-01T08:51:12.815Z","_id":"cioiqegf9004bz8mbk5u820vc","layout":"post","photos":[],"link":""},{"title":"OpenFOAM 中的单相流湍流模型之SpalartAllmaras","date":"2016-03-06T11:13:58.000Z","comments":1,"_content":"本篇简要分析不可压缩的 SpalartAllmaras 模型的代码。主要内容包括模型输运方程的代码说明，以及一些使用方面的细节。\n\n<!--more-->\n\n###  湍流模型代码实例\n这一部分分析几个 OpenFOAM 里自带的湍流模型， 并探讨修改或者添加新湍流模型的方法。\n\n#### 1 SpalartAllmaras 模型\n#####1.1. 模型分析\nSpalartAllmaras 模型是一方程模型，它只需要求解一个输运方程。OpenFOAM 中的 SpalartAllmaras 模型是在原始论文[1] 的基础上，引入了 Ashford [2] 对这个模型的修正。下面来看这个模型在 OpenFOAM 中的实现，代码位于 `src/turbulenceModels/incompressible/RAS/SpalartAllmaras` \n首先是头文件， `SpalartAllmaras.H` \n头文件开始，声明了一个类：SpalartAllmaras\n```\nclass SpalartAllmaras\n:\n    public RASModel\n```\n说明这个类是公有继承 `RASModel` 类的。\n接下来是一些数据成员以及成员函数的声明，这里无需赘述，直接来看函数的具体定义吧。\n函数的具体定义，在`SpalartAllmaras.C` 里（注意到 OpenFOAM 的代码几乎都是这样讲声明和定义分开，这样做的目的，是为了防止重复声明的问题，更详细的说明，见我的[另一篇博文](http://xiaopingqiu.github.io/2016/03/06/separationOfDeclarationAndDefiniton/)）。注意，湍流模型的定义，最终是为了在求解器中进行调用的，所以，先来回顾一下湍流模型贡献给求解器中动量方程中那一项：\n```\nturbulence->divDevReff(U)\n```\n这一项，不同求解器可能会有不同，但大致的形式是这样的。\n很显然，这个 `divDevReff` 应该是湍流模型类的成员函数，实际也的确如此， `SpalartAllmaras.C` 有这个函数的定义：\n```\ntmp<fvVectorMatrix> SpalartAllmaras::divDevReff(volVectorField& U) const\n{\n    const volScalarField nuEff_(nuEff());\n\n    return\n    (\n      - fvm::laplacian(nuEff_, U)\n      - fvc::div(nuEff_*dev(T(fvc::grad(U))))\n    );\n}\n```\n这个函数，写成公式是如下形式\n$$\n-\\nabla \\cdot (\\nu\\_{eff}\\nabla U)-\\nabla \\cdot [\\nu\\_{eff}dev(\\nabla ^TU)]\n$$\nOpenFOAM 中的 `dev`运算符定义为 $dev(T)=T-\\frac{1}{3}tr(T)I$ ， `tr` 为张量的迹，即主对角元素之和所以。对于张量 $\\nabla ^TU$ ，$tr(\\nabla ^TU) =\\nabla \\cdot U$，因此上式可以写为【注一】\n$$\n-\\nabla \\cdot (\\nu\\_{eff}\\nabla U)-\\nabla \\cdot [\\nu\\_{eff}(\\nabla^TU-\\frac{1}{3}(\\nabla \\cdot U))]\n$$\n上述 `divDevReff` 函数中，变量 `nuEff_` d的值是函数 `nuEff()` 的返回值，但是，在 `SpalartAllmaras.C` 中却未见 `nuEff()` 函数的定义。这里就要记住了，C++ 的类继承的一个特性是派生类会从基类继承其中定义的成员函数。所以看到本类中没定义的函数时，**don't panic**，往基类找就是了。翻看基类 `RASModel` 的代码，可以很容易从 `RASModel.C` 中找出来 `nuEff()` 的定义：\n```\nvirtual tmp<volScalarField> nuEff() const  \n{\n    return tmp<volScalarField>\n    (\n        new volScalarField(\"nuEff\", nut() + nu())\n    );\n}\n```\n 函数 `nut()` 的定义可以从 `SpalartAllmaras.H` 中找到，可是 `nu()` 呢？虽然从名字可以猜到它返回的是分子粘度（可见变量命名的重要性啊），可是，它的定义在哪里？ `RASModel.C` 也没有啊...\n答案是，继续向上找，RASModel 类没有，那就去更上一层的基类，turbulenceModel。果然，在 `turbulenceModel.C` 中能找到其定义：\n```\ninline tmp<volScalarField> nu() const\n    {\n        return transportModel_.nu();\n    }\n```\n `transportModel_` 又是什么鬼？这个不是本篇博文的内容，该回到正题了，这里应该关注的是湍流粘度 `nut_` 。\n湍流模型不可能仅仅是给求解器贡献那一项，湍流粘度应该也随着求解器的运行而更新。是这样的，求解器中的\n```\nturbulence->correct();\n```\n这句代码负责湍流粘度的更新。这里又涉及到一个湍流类的成员函数： `corretc`。这个函数是湍流模型类的核心，因为湍流模型输运方程的求解，湍流粘度的更新都在这个函数里面。下面来看 `SpalartAllmaras` 模型的 `correct` 函数：\n```\nvoid SpalartAllmaras::correct()\n{\n    RASModel::correct();\n    if (!turbulence_)\n    {\n        // Re-calculate viscosity\n        nut_ = nuTilda_*fv1(this->chi());\n        nut_.correctBoundaryConditions();\n\n        return;\n    }\n    if (mesh_.changing())\n    {\n        d_.correct();\n    }\n\n    const volScalarField chi(this->chi());\n    const volScalarField fv1(this->fv1(chi));\n\n    const volScalarField Stilda\n    (\n        fv3(chi, fv1)*::sqrt(2.0)*mag(skew(fvc::grad(U_)))\n      + fv2(chi, fv1)*nuTilda_/sqr(kappa_*d_)\n    );\n\n    tmp<fvScalarMatrix> nuTildaEqn\n    (\n        fvm::ddt(nuTilda_)\n      + fvm::div(phi_, nuTilda_)\n      - fvm::laplacian(DnuTildaEff(), nuTilda_)\n      - Cb2_/sigmaNut_*magSqr(fvc::grad(nuTilda_))\n     ==\n        Cb1_*Stilda*nuTilda_\n      - fvm::Sp(Cw1_*fw(Stilda)*nuTilda_/sqr(d_), nuTilda_)\n    );\n\n    nuTildaEqn().relax();\n    solve(nuTildaEqn);\n    bound(nuTilda_, dimensionedScalar(\"0\", nuTilda_.dimensions(), 0.0));\n    nuTilda_.correctBoundaryConditions();\n\n    // Re-calculate viscosity\n    nut_.internalField() = fv1*nuTilda_.internalField();\n    nut_.correctBoundaryConditions();\n}\n```\n先看核心的输运方程， `nuTildaEqn` ，这个部分的每一行写成公式，分别如下：\n$$\n\\frac{\\partial \\tilde{\\nu}}{\\partial t} \\\\\\\\\n\\nabla \\cdot (U \\tilde{\\nu}) \\\\\\\\\n-\\nabla \\cdot [D\\_{\\tilde{\\nu}\\_{eff}} \\nabla \\tilde{\\nu}] \\\\\\\\\n-\\frac{C\\_{b2}}{\\sigma\\_{\\nu\\_t}} \\cdot |\\nabla \\tilde{\\nu}|^2 \\\\\\\\\nC\\_{b1}\\cdot \\tilde{S} \\cdot \\tilde{\\nu} \\\\\\\\\n-\\frac{C\\_{w1}\\cdot f\\_w\\cdot \\tilde{\\nu}}{d^2} \\cdot \\tilde{\\nu}\\\\\\\\\n$$\n\n最后两项都包含了需要求解的量 $\\tilde{\\nu}$， 但是处理方式却不一样，最后一项，其实是将原本的 $\\tilde{\\nu} ^2$ 项进行了线性化，并用 `fvm::Sp` 操作符进行隐式处理。而倒数第二项，则是作的显式处理。差别在于，倒数第二项仅仅是用上一步得到的 $\\tilde{\\nu}$ 代入，然后这一项被加到 $Ax=b$ 中的 $b$ 部分。而隐式处理则会对系数矩阵 $A$ 有贡献。\n输运方程中涉及到的变量和函数，全部都在 `SpalartAllmaras` 类中有定义，这里就不再单独分析了，仅将涉及到的公式列出如下\n$$\nD\\_{\\tilde{\\nu}\\_{eff}}=\\frac{\\nu+\\tilde{\\nu}}{\\sigma\\_{\\nu\\_t}}\n$$\n$$\nf\\_w=g\\cdot \\left [ \\frac{1+C\\_{w3}^6}{g^6+C\\_{w3}^6} \\right]^{1/6},\\quad  g=r+C\\_{w2}\\cdot(r^6-r) \\\\\\\\\nr=min\\left[ \\frac{\\tilde{\\nu}}{max(\\tilde{S}, SMALL)\\cdot \\kappa^2 d^2},10 \\right]\n$$\n\n$$\n\\tilde{S}=f\\_{v3}\\cdot\\sqrt{2\\Omega\\_{ij}:\\Omega\\_{ij}}+\\frac{f\\_{v2}\\tilde{\\nu}}{\\kappa^2 d^2},\\quad \\Omega\\_{ij}=\\frac{1}{2}(\\nabla U -\\nabla ^T U)\n$$\n\n$f\\_{v2}$ 和 $f\\_{v3}$ 有点特殊，因为涉及到了 Ashford 对原始模型的修正。根据作者的论文，修正的目的是防止 $\\tilde{S}$ 出现负值。\n如果不引入 Ashford 修正，则 $f\\_{v3}=1$；引入以后，则\n$$\nf\\_{v3}=\\frac{(1+\\chi \\cdot f\\_{v1})}{c\\_{v2}}\\cdot \\frac{3+\\frac{\\chi}{c\\_{v2}} + (\\frac{\\chi}{c\\_{v2}})^2 }{(1+\\frac{\\chi}{c\\_{v2}})^3}\n$$\n对于 $f\\_{v2}$，如果不引入 Ashford 修正，则\n$$\nf\\_{v2}=\\frac{1}{(1+\\frac{\\chi}{c\\_{v2}})^3}\n$$\n若引入，则\n$$\nf\\_{v2}=1-\\frac{\\chi}{1+\\chi f\\_{v1}}\n$$\n此外，\n$$\nf\\_{v1}=\\frac{\\chi ^3}{\\chi ^3 + c\\_{v1}^3}, \\quad  \\chi=\\frac{\\tilde{\\nu}}{\\nu}, \\quad \\nu\\_t = \\tilde{\\nu}\nf\\_{v1}$$\n$d$ 是与壁面的距离，有一个专门的类 `wallDist` 来计算它。\n其他的没有提到的变量，则都是模型常数了。具体的值都在 `SpalartAllmaras` 类的构造函数中定义了，这里就不再赘述了。\n\n#####1.2. 一些细节\n1.  `turbulence_` 这个变量\n `correct` 函数中，只有当 `turbulence_` 为 `true` 时，下面求解输运方程的部分代码才会执行。这个变量又是从 `RASModel` 类继承过来的，其初始化的语句为 `turbulence_(lookup(\"turbulence\"))` ，这意味着，这个变量的值是从 `RASProperties` 文件中读取的（详见文末的 `RASProperties` 示例）。\n2. 模型常数，除了使用默认值，还可以自己指定（注：除非很有把握，一般不要随便修改模型常数）。具体方法是，在 `RASProperties` 文件里，建立一个名为 `SpalartAllmarasCoeffs` 的 subDict，详见文末的示例。 \n3.  `bound` 函数\n这个函数的定义在头文件 `bound.H` 里，其功能是限制 $\\tilde{\\nu}$ 的最小值。不过这种限制其实没有多少物理意义，纯粹是数值技巧。其中用到了 `fvc::average` 函数（见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)）。\n4. `read` 成员函数\n这个成员函数，初看之下似乎跟模型常数的初始化有关，实则不然。模型常数的初始化在类的构造函数部分完成， `coeffDict_` 这个变量在基类 `RASModel` 的构造函数中先用 `type()+Coeffs` (这里是 `SpalartAllmarasCoeffs` )这个 subDict 初始化了，然后，在 `SpalartAllmaras` 类的构造函数中，每一个模型常数都是先去 `coeffDict_` 中查找是否用用户自定义的值，如果没有，则用模型的默认值，并且将这个默认值添加到 `coeffDict_` 这个字典中。\n5. 其他成员函数\n`SpalartAllmaras` 显然不需要用到湍动能 `k` 以及湍动能耗散 `epsilon` ，但是在 `SpalartAllmaras.C` 中确有这两个函数的定义，而且返回值是0。这是因为，在基类 `turbulenceModel` 中， `k()`， `epsilon()` 函数被声明为纯虚函数，如果不在 `SpalartAllmaras` 中重新定义这两个函数，则 `SpalartAllmaras` 也将是包括纯虚函数的抽象类了。在 C++ 中，抽象类是无法建立对象的。\n `devReff`， `R` ， `divDevRhoReff` 这几个成员函数，是湍流模型提供给其他模型调用的函数，比如，在计算表面应力的 `forces` 类中就需要调用湍流模型的 `devReff` 函数。\n\n##### 附： RASProperties 文件示例\n```\n/*--------------------------------*- C++ -*----------------------------------*\\\n| =========                 |                                                 |\n| \\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |\n|  \\\\    /   O peration     | Version:  2.3.1                                 |\n|   \\\\  /    A nd           | Web:      www.OpenFOAM.org                      |\n|    \\\\/     M anipulation  |                                                 |\n\\*---------------------------------------------------------------------------*/\nFoamFile\n{\n    version     2.0;\n    format      ascii;\n    class       dictionary;\n    location    \"constant\";\n    object      RASProperties;\n}\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\nRASModel        SpalartAllmaras ;\nturbulence      on; //若为 off，则湍流模型的输运方程将不会求解！\nprintCoeffs     on; // 输出模型常数\n\nSpalartAllmarasCoeffs // 设置模型常数(仅为示例，具体数值不要较真)\n{\n    Cw2       0.20;\n    Cw3       0.10;\n}\n\n// ************************************************************************* //\n```\n\n【注一】：N-S 方程中，这一项应该是\n$$\n-\\nabla \\cdot (\\nu\\_{eff}\\nabla U)-\\nabla \\cdot [\\nu\\_{eff}(\\nabla^TU-\\frac{2}{3}(\\nabla \\cdot U))]\n$$\n而且，在可压缩的湍流模型中， `divDevReff` 函数用的是 `dev2` 函数，所以其定义的形式就跟 N-S 方程一致了。但不知道为什么不可压缩的湍流模型为什么要用 `dev` 函数。虽然对于不可压缩的情形，由于 $\\nabla \\cdot U=0$，用 `dev` 或者 `dev2` 应该关系不大，但这种做法仍然很费解。在OpenFOAM-3.0版本中，可压和不可压用的都是 `dev2` 函数了。\n\n##### 参考：\n[1] P. Spalart and S. Allmaras. \"A one-equation turbulence model for aerodynamic flows\". Technical Report AIAA-92-0439. American Institute of Aeronautics and Astronautics. 1992.\n\n[2] \"An Unstructured Grid Generation and Adaptive Solution Technique         for High Reynolds Number Compressible Flows\", G.A. Ashford, Ph.D. thesis, University of Michigan, 1996.\n\n","source":"_posts/OpenFOAM-singlePhase-turbulenceModel2.md","raw":"title: \"OpenFOAM 中的单相流湍流模型之SpalartAllmaras\"\ndate: 2016-03-06 19:13:58\ncomments: true\ntags:\n - OpenFOAM\n - Code Explained\ncategories:\n - OpenFOAM\n---\n本篇简要分析不可压缩的 SpalartAllmaras 模型的代码。主要内容包括模型输运方程的代码说明，以及一些使用方面的细节。\n\n<!--more-->\n\n###  湍流模型代码实例\n这一部分分析几个 OpenFOAM 里自带的湍流模型， 并探讨修改或者添加新湍流模型的方法。\n\n#### 1 SpalartAllmaras 模型\n#####1.1. 模型分析\nSpalartAllmaras 模型是一方程模型，它只需要求解一个输运方程。OpenFOAM 中的 SpalartAllmaras 模型是在原始论文[1] 的基础上，引入了 Ashford [2] 对这个模型的修正。下面来看这个模型在 OpenFOAM 中的实现，代码位于 `src/turbulenceModels/incompressible/RAS/SpalartAllmaras` \n首先是头文件， `SpalartAllmaras.H` \n头文件开始，声明了一个类：SpalartAllmaras\n```\nclass SpalartAllmaras\n:\n    public RASModel\n```\n说明这个类是公有继承 `RASModel` 类的。\n接下来是一些数据成员以及成员函数的声明，这里无需赘述，直接来看函数的具体定义吧。\n函数的具体定义，在`SpalartAllmaras.C` 里（注意到 OpenFOAM 的代码几乎都是这样讲声明和定义分开，这样做的目的，是为了防止重复声明的问题，更详细的说明，见我的[另一篇博文](http://xiaopingqiu.github.io/2016/03/06/separationOfDeclarationAndDefiniton/)）。注意，湍流模型的定义，最终是为了在求解器中进行调用的，所以，先来回顾一下湍流模型贡献给求解器中动量方程中那一项：\n```\nturbulence->divDevReff(U)\n```\n这一项，不同求解器可能会有不同，但大致的形式是这样的。\n很显然，这个 `divDevReff` 应该是湍流模型类的成员函数，实际也的确如此， `SpalartAllmaras.C` 有这个函数的定义：\n```\ntmp<fvVectorMatrix> SpalartAllmaras::divDevReff(volVectorField& U) const\n{\n    const volScalarField nuEff_(nuEff());\n\n    return\n    (\n      - fvm::laplacian(nuEff_, U)\n      - fvc::div(nuEff_*dev(T(fvc::grad(U))))\n    );\n}\n```\n这个函数，写成公式是如下形式\n$$\n-\\nabla \\cdot (\\nu\\_{eff}\\nabla U)-\\nabla \\cdot [\\nu\\_{eff}dev(\\nabla ^TU)]\n$$\nOpenFOAM 中的 `dev`运算符定义为 $dev(T)=T-\\frac{1}{3}tr(T)I$ ， `tr` 为张量的迹，即主对角元素之和所以。对于张量 $\\nabla ^TU$ ，$tr(\\nabla ^TU) =\\nabla \\cdot U$，因此上式可以写为【注一】\n$$\n-\\nabla \\cdot (\\nu\\_{eff}\\nabla U)-\\nabla \\cdot [\\nu\\_{eff}(\\nabla^TU-\\frac{1}{3}(\\nabla \\cdot U))]\n$$\n上述 `divDevReff` 函数中，变量 `nuEff_` d的值是函数 `nuEff()` 的返回值，但是，在 `SpalartAllmaras.C` 中却未见 `nuEff()` 函数的定义。这里就要记住了，C++ 的类继承的一个特性是派生类会从基类继承其中定义的成员函数。所以看到本类中没定义的函数时，**don't panic**，往基类找就是了。翻看基类 `RASModel` 的代码，可以很容易从 `RASModel.C` 中找出来 `nuEff()` 的定义：\n```\nvirtual tmp<volScalarField> nuEff() const  \n{\n    return tmp<volScalarField>\n    (\n        new volScalarField(\"nuEff\", nut() + nu())\n    );\n}\n```\n 函数 `nut()` 的定义可以从 `SpalartAllmaras.H` 中找到，可是 `nu()` 呢？虽然从名字可以猜到它返回的是分子粘度（可见变量命名的重要性啊），可是，它的定义在哪里？ `RASModel.C` 也没有啊...\n答案是，继续向上找，RASModel 类没有，那就去更上一层的基类，turbulenceModel。果然，在 `turbulenceModel.C` 中能找到其定义：\n```\ninline tmp<volScalarField> nu() const\n    {\n        return transportModel_.nu();\n    }\n```\n `transportModel_` 又是什么鬼？这个不是本篇博文的内容，该回到正题了，这里应该关注的是湍流粘度 `nut_` 。\n湍流模型不可能仅仅是给求解器贡献那一项，湍流粘度应该也随着求解器的运行而更新。是这样的，求解器中的\n```\nturbulence->correct();\n```\n这句代码负责湍流粘度的更新。这里又涉及到一个湍流类的成员函数： `corretc`。这个函数是湍流模型类的核心，因为湍流模型输运方程的求解，湍流粘度的更新都在这个函数里面。下面来看 `SpalartAllmaras` 模型的 `correct` 函数：\n```\nvoid SpalartAllmaras::correct()\n{\n    RASModel::correct();\n    if (!turbulence_)\n    {\n        // Re-calculate viscosity\n        nut_ = nuTilda_*fv1(this->chi());\n        nut_.correctBoundaryConditions();\n\n        return;\n    }\n    if (mesh_.changing())\n    {\n        d_.correct();\n    }\n\n    const volScalarField chi(this->chi());\n    const volScalarField fv1(this->fv1(chi));\n\n    const volScalarField Stilda\n    (\n        fv3(chi, fv1)*::sqrt(2.0)*mag(skew(fvc::grad(U_)))\n      + fv2(chi, fv1)*nuTilda_/sqr(kappa_*d_)\n    );\n\n    tmp<fvScalarMatrix> nuTildaEqn\n    (\n        fvm::ddt(nuTilda_)\n      + fvm::div(phi_, nuTilda_)\n      - fvm::laplacian(DnuTildaEff(), nuTilda_)\n      - Cb2_/sigmaNut_*magSqr(fvc::grad(nuTilda_))\n     ==\n        Cb1_*Stilda*nuTilda_\n      - fvm::Sp(Cw1_*fw(Stilda)*nuTilda_/sqr(d_), nuTilda_)\n    );\n\n    nuTildaEqn().relax();\n    solve(nuTildaEqn);\n    bound(nuTilda_, dimensionedScalar(\"0\", nuTilda_.dimensions(), 0.0));\n    nuTilda_.correctBoundaryConditions();\n\n    // Re-calculate viscosity\n    nut_.internalField() = fv1*nuTilda_.internalField();\n    nut_.correctBoundaryConditions();\n}\n```\n先看核心的输运方程， `nuTildaEqn` ，这个部分的每一行写成公式，分别如下：\n$$\n\\frac{\\partial \\tilde{\\nu}}{\\partial t} \\\\\\\\\n\\nabla \\cdot (U \\tilde{\\nu}) \\\\\\\\\n-\\nabla \\cdot [D\\_{\\tilde{\\nu}\\_{eff}} \\nabla \\tilde{\\nu}] \\\\\\\\\n-\\frac{C\\_{b2}}{\\sigma\\_{\\nu\\_t}} \\cdot |\\nabla \\tilde{\\nu}|^2 \\\\\\\\\nC\\_{b1}\\cdot \\tilde{S} \\cdot \\tilde{\\nu} \\\\\\\\\n-\\frac{C\\_{w1}\\cdot f\\_w\\cdot \\tilde{\\nu}}{d^2} \\cdot \\tilde{\\nu}\\\\\\\\\n$$\n\n最后两项都包含了需要求解的量 $\\tilde{\\nu}$， 但是处理方式却不一样，最后一项，其实是将原本的 $\\tilde{\\nu} ^2$ 项进行了线性化，并用 `fvm::Sp` 操作符进行隐式处理。而倒数第二项，则是作的显式处理。差别在于，倒数第二项仅仅是用上一步得到的 $\\tilde{\\nu}$ 代入，然后这一项被加到 $Ax=b$ 中的 $b$ 部分。而隐式处理则会对系数矩阵 $A$ 有贡献。\n输运方程中涉及到的变量和函数，全部都在 `SpalartAllmaras` 类中有定义，这里就不再单独分析了，仅将涉及到的公式列出如下\n$$\nD\\_{\\tilde{\\nu}\\_{eff}}=\\frac{\\nu+\\tilde{\\nu}}{\\sigma\\_{\\nu\\_t}}\n$$\n$$\nf\\_w=g\\cdot \\left [ \\frac{1+C\\_{w3}^6}{g^6+C\\_{w3}^6} \\right]^{1/6},\\quad  g=r+C\\_{w2}\\cdot(r^6-r) \\\\\\\\\nr=min\\left[ \\frac{\\tilde{\\nu}}{max(\\tilde{S}, SMALL)\\cdot \\kappa^2 d^2},10 \\right]\n$$\n\n$$\n\\tilde{S}=f\\_{v3}\\cdot\\sqrt{2\\Omega\\_{ij}:\\Omega\\_{ij}}+\\frac{f\\_{v2}\\tilde{\\nu}}{\\kappa^2 d^2},\\quad \\Omega\\_{ij}=\\frac{1}{2}(\\nabla U -\\nabla ^T U)\n$$\n\n$f\\_{v2}$ 和 $f\\_{v3}$ 有点特殊，因为涉及到了 Ashford 对原始模型的修正。根据作者的论文，修正的目的是防止 $\\tilde{S}$ 出现负值。\n如果不引入 Ashford 修正，则 $f\\_{v3}=1$；引入以后，则\n$$\nf\\_{v3}=\\frac{(1+\\chi \\cdot f\\_{v1})}{c\\_{v2}}\\cdot \\frac{3+\\frac{\\chi}{c\\_{v2}} + (\\frac{\\chi}{c\\_{v2}})^2 }{(1+\\frac{\\chi}{c\\_{v2}})^3}\n$$\n对于 $f\\_{v2}$，如果不引入 Ashford 修正，则\n$$\nf\\_{v2}=\\frac{1}{(1+\\frac{\\chi}{c\\_{v2}})^3}\n$$\n若引入，则\n$$\nf\\_{v2}=1-\\frac{\\chi}{1+\\chi f\\_{v1}}\n$$\n此外，\n$$\nf\\_{v1}=\\frac{\\chi ^3}{\\chi ^3 + c\\_{v1}^3}, \\quad  \\chi=\\frac{\\tilde{\\nu}}{\\nu}, \\quad \\nu\\_t = \\tilde{\\nu}\nf\\_{v1}$$\n$d$ 是与壁面的距离，有一个专门的类 `wallDist` 来计算它。\n其他的没有提到的变量，则都是模型常数了。具体的值都在 `SpalartAllmaras` 类的构造函数中定义了，这里就不再赘述了。\n\n#####1.2. 一些细节\n1.  `turbulence_` 这个变量\n `correct` 函数中，只有当 `turbulence_` 为 `true` 时，下面求解输运方程的部分代码才会执行。这个变量又是从 `RASModel` 类继承过来的，其初始化的语句为 `turbulence_(lookup(\"turbulence\"))` ，这意味着，这个变量的值是从 `RASProperties` 文件中读取的（详见文末的 `RASProperties` 示例）。\n2. 模型常数，除了使用默认值，还可以自己指定（注：除非很有把握，一般不要随便修改模型常数）。具体方法是，在 `RASProperties` 文件里，建立一个名为 `SpalartAllmarasCoeffs` 的 subDict，详见文末的示例。 \n3.  `bound` 函数\n这个函数的定义在头文件 `bound.H` 里，其功能是限制 $\\tilde{\\nu}$ 的最小值。不过这种限制其实没有多少物理意义，纯粹是数值技巧。其中用到了 `fvc::average` 函数（见我的[另一篇博文](http://xiaopingqiu.github.io/2015/05/17/OpenFOAMcode1/)）。\n4. `read` 成员函数\n这个成员函数，初看之下似乎跟模型常数的初始化有关，实则不然。模型常数的初始化在类的构造函数部分完成， `coeffDict_` 这个变量在基类 `RASModel` 的构造函数中先用 `type()+Coeffs` (这里是 `SpalartAllmarasCoeffs` )这个 subDict 初始化了，然后，在 `SpalartAllmaras` 类的构造函数中，每一个模型常数都是先去 `coeffDict_` 中查找是否用用户自定义的值，如果没有，则用模型的默认值，并且将这个默认值添加到 `coeffDict_` 这个字典中。\n5. 其他成员函数\n`SpalartAllmaras` 显然不需要用到湍动能 `k` 以及湍动能耗散 `epsilon` ，但是在 `SpalartAllmaras.C` 中确有这两个函数的定义，而且返回值是0。这是因为，在基类 `turbulenceModel` 中， `k()`， `epsilon()` 函数被声明为纯虚函数，如果不在 `SpalartAllmaras` 中重新定义这两个函数，则 `SpalartAllmaras` 也将是包括纯虚函数的抽象类了。在 C++ 中，抽象类是无法建立对象的。\n `devReff`， `R` ， `divDevRhoReff` 这几个成员函数，是湍流模型提供给其他模型调用的函数，比如，在计算表面应力的 `forces` 类中就需要调用湍流模型的 `devReff` 函数。\n\n##### 附： RASProperties 文件示例\n```\n/*--------------------------------*- C++ -*----------------------------------*\\\n| =========                 |                                                 |\n| \\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |\n|  \\\\    /   O peration     | Version:  2.3.1                                 |\n|   \\\\  /    A nd           | Web:      www.OpenFOAM.org                      |\n|    \\\\/     M anipulation  |                                                 |\n\\*---------------------------------------------------------------------------*/\nFoamFile\n{\n    version     2.0;\n    format      ascii;\n    class       dictionary;\n    location    \"constant\";\n    object      RASProperties;\n}\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\nRASModel        SpalartAllmaras ;\nturbulence      on; //若为 off，则湍流模型的输运方程将不会求解！\nprintCoeffs     on; // 输出模型常数\n\nSpalartAllmarasCoeffs // 设置模型常数(仅为示例，具体数值不要较真)\n{\n    Cw2       0.20;\n    Cw3       0.10;\n}\n\n// ************************************************************************* //\n```\n\n【注一】：N-S 方程中，这一项应该是\n$$\n-\\nabla \\cdot (\\nu\\_{eff}\\nabla U)-\\nabla \\cdot [\\nu\\_{eff}(\\nabla^TU-\\frac{2}{3}(\\nabla \\cdot U))]\n$$\n而且，在可压缩的湍流模型中， `divDevReff` 函数用的是 `dev2` 函数，所以其定义的形式就跟 N-S 方程一致了。但不知道为什么不可压缩的湍流模型为什么要用 `dev` 函数。虽然对于不可压缩的情形，由于 $\\nabla \\cdot U=0$，用 `dev` 或者 `dev2` 应该关系不大，但这种做法仍然很费解。在OpenFOAM-3.0版本中，可压和不可压用的都是 `dev2` 函数了。\n\n##### 参考：\n[1] P. Spalart and S. Allmaras. \"A one-equation turbulence model for aerodynamic flows\". Technical Report AIAA-92-0439. American Institute of Aeronautics and Astronautics. 1992.\n\n[2] \"An Unstructured Grid Generation and Adaptive Solution Technique         for High Reynolds Number Compressible Flows\", G.A. Ashford, Ph.D. thesis, University of Michigan, 1996.\n\n","slug":"OpenFOAM-singlePhase-turbulenceModel2","published":1,"updated":"2016-03-06T13:35:35.300Z","layout":"post","photos":[],"link":"","_id":"cioiqegfe004fz8mb2gsy72pq"},{"title":"OpenFOAM 中的单相流湍流模型之一","date":"2015-11-25T11:13:58.000Z","comments":1,"_content":"\n相信有不少 OpenFOAM 用户有添加湍流模型的需求，我自己最早用 OpenFOAM 完成的一项工作就是在其中添加了一些单相流的湍流模型，并进行了一些计算。这里将我对单相湍流模型代码框架的理解记录下来，供大家参考。本系列将包含三篇，第一篇介绍湍流模型类的继承派生关系，第二篇具体分析几个 OpenFOAM 中带的湍流模型，并给出修改或增加新模型的方法，第三篇分析湍流模型的运行时选择机制（Run Time Selection）的原理。\n\n<!--more-->\n\n#### 1. 湍流模型类的继承派生关系\n这一部分是最简单的，只要有一点`C++`的知识，看一下湍流模型的代码头文件的类声明部分，就能理解。OpenFOAM 里的单相湍流模型包含两大类，`RAS` 和 `LES`，下面将分别分析。\n\nOpenFOAM 单相流湍流模型的代码在 `src/turbulenceModels` 目录下，目录结构如下：\n```\n Allwmake  compressible  derivedFvPatchFields  incompressible  LES\n```\n其中， `compressible` 和 `incompressible` 分别是可压缩和不可压缩湍流模型的代码， `derivedFvPatchFields` 是两个湍流相关的边界条件的代码， `LES` 是大涡模拟的两个相关的类（ `LESdeltas` 和 `LESfilters` ），具体在后面会介绍。这里我主要关心不可压缩湍流模型。\n进入`incompressible`，目录结构为：\n```\n$  cd  incompressible\n$  ls\nAllwmake  LES  RAS  turbulenceModel\n```\n这里， 目录`turbulenceModel` 里是基类 `turbulenceModel` 相关的代码， `RAS` 和 `LES` ，顾名思义，分别是雷诺时均和大涡模拟湍流模型的代码。\n\n##### 1.1 基类 `turbulenceModel` \n首先看基类 `turbulenceModel` ，这里我挑着我觉得重要的部分代码列出来：\n\n先看头文件 turbulenceModel.H：\n```\nnamespace Foam\n{\n\n// incompressible 命名空间，注意这个是很重要的，作用是将类隔离开。比如，可压和不可压都有kEpsilon模型，这两个模型的类名是一样的。要区分这两个类，靠的就是这个命名空间。可压的是 compressible::kEpsilon, 而不可压的则是 incompressible::kEpsilon\nnamespace incompressible \n{\n\nclass turbulenceModel //定义一个 turbulenceModel 类，继承自 regIOobject 类\n:\n    public regIOobject\n{\n\nprotected:\n\n    // 数据成员\n        const Time& runTime_;\n        const fvMesh& mesh_;\n\n        const volVectorField& U_;\n        const surfaceScalarField& phi_;\n\n        transportModel& transportModel_; // 输运模型，涉及到分子粘度的设置\n\n        //- Near wall distance boundary field\n        nearWallDist y_;\n\npublic:\n\n    //- Runtime type information\n    TypeName(\"turbulenceModel\");\n\n\n    // Declare run-time New selection table 这个跟运行时选择有关，具体以后会涉及\n\n        declareRunTimeNewSelectionTable\n        (\n            autoPtr,\n            turbulenceModel,\n            turbulenceModel,\n            (\n                const volVectorField& U,\n                const surfaceScalarField& phi,\n                transportModel& transport,\n                const word& turbulenceModelName\n            ),\n            (U, phi, transport, turbulenceModelName)\n        );\n\n\n    // Constructors  构造函数\n\n        //- Construct from components\n        turbulenceModel\n        (\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = typeName\n        );\n\n\n    // Selectors // 这个也是跟运行时选择机制有关，涉及到具体湍流模型的选择过程\n        //- Return a reference to the selected turbulence model\n        static autoPtr<turbulenceModel> New\n        (\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = typeName\n        );\n\n\n    // Member Functions\n\n        //- Const access to the coefficients dictionary\n        virtual const dictionary& coeffDict() const = 0;\n\n        //- Helper function to return the nam eof the turbulence G field\n        inline word GName() const\n        {\n            return word(type() + \":G\");\n        }\n\n        //- Access function to velocity field\n        inline const volVectorField& U() const\n        {\n            return U_;\n        }\n\n        //- Access function to flux field\n        inline const surfaceScalarField& phi() const\n        {\n            return phi_;\n        }\n\n        //- Access function to incompressible transport model\n        inline transportModel& transport() const\n        {\n            return transportModel_;\n        }\n\n        //- Return the near wall distances\n        const nearWallDist& y() const\n        {\n            return y_;\n        }\n\n        //- Return the laminar viscosity\n\t// 分子粘度，注意这里的返回值是输运模型类对象的 nu 函数的返回值。\n\t// 具体来说，如果是牛顿流体，那么这个返回值就是我们在transportProperties里设置的粘度；\n\t// 如果是非牛顿流体，那么粘度是根据具体的非牛顿流体模型计算得到的。\n        inline tmp<volScalarField> nu() const \n        {\n            return transportModel_.nu();\n        }\n\n    // 以下形如 \"virtual xxxx () const = 0\" 的函数，都是纯虚函数，这是很重要的一部分。\n    // 当一个类中有纯虚函数时，这个类就被称作抽象类，抽象类本身不能建立对象，一般都是作为接口类来使用。\n    // 这里的turbulenceModel类就是接口类，“接口类”三个字的具体含义后面会解释。\n        //- Return the turbulence viscosity\n        virtual tmp<volScalarField> nut() const = 0;\n        //- Return the effective viscosity\n        virtual tmp<volScalarField> nuEff() const = 0;\n        //- Return the turbulence kinetic energy\n        virtual tmp<volScalarField> k() const = 0;\n        //- Return the turbulence kinetic energy dissipation rate\n        virtual tmp<volScalarField> epsilon() const = 0;\n        //- Return the Reynolds stress tensor\n        virtual tmp<volSymmTensorField> R() const = 0;\n        //- Return the effective stress tensor including the laminar stress\n        virtual tmp<volSymmTensorField> devReff() const = 0;\n        //- Return the source term for the momentum equation\n        virtual tmp<fvVectorMatrix> divDevReff(volVectorField& U) const = 0;\n        //- Return the source term for the momentum equation\n        virtual tmp<fvVectorMatrix> divDevRhoReff\n        (\n            const volScalarField& rho,\n            volVectorField& U\n        ) const = 0;\n        //- Solve the turbulence equations and correct the turbulence viscosity\n        virtual void correct() = 0;\n\n        //- Read LESProperties or RASProperties dictionary\n        virtual bool read() = 0;\n};\n```\n再来看 turbulenceModel.C，重点关注构造函数和 New 函数\n```\nnamespace Foam\n{\nnamespace incompressible\n{\n\n// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * \n\n//这句与运行时选择机制有关，后面再说\ndefineRunTimeSelectionTable(turbulenceModel, turbulenceModel);\n\n//构造函数的定义。构造函数包括四个参数，其中最后一个\"turbulenceModelName\"是带缺省参数的，所以，只需要提供三个参数。\nturbulenceModel::turbulenceModel\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n//  这里往下一段叫做成员初始化列表，用于对当前类以及其基类成员进行初始化\n:\n    regIOobject\n    (\n        IOobject\n        (\n            turbulenceModelName,\n            U.time().constant(),\n            U.db(),\n            IOobject::NO_READ,\n            IOobject::NO_WRITE\n        )\n    ),\n    runTime_(U.time()),\n    mesh_(U.mesh()),\n\n    U_(U),\n    phi_(phi),\n    transportModel_(transport), // 输运模型从构造函数的参数中读取\n    y_(mesh_)\n{}\n\n\n// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //\n\n这个函数，起着选择具体湍流模型的作用，后面我会结合求解器代码仔细说说这个函数。更详细的机制，将在结合运行时选择机制来解释。\nautoPtr<turbulenceModel> turbulenceModel::New\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n{\n    \n    // 注意这里了，算例中的\"turbulenceProperties\" 文件即由这段代码来读取。\n    // 需要从\"turbulenceProperties\" 文件中查找一个关键字\"simulationType\"，\n    // 然后将\"simulationType\"对应的值赋值给变量\"modelType\"（对于单相流，modelType 只可能是 \"RAS\" 或者 \"LES\"）。\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                \"turbulenceProperties\",\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).lookup(\"simulationType\")\n    );\n\n    Info<< \"Selecting turbulence model type \" << modelType << endl;\n\n    turbulenceModelConstructorTable::iterator cstrIter =\n        turbulenceModelConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == turbulenceModelConstructorTablePtr_->end())\n    {\n        FatalErrorIn\n        (\n            \"turbulenceModel::New(const volVectorField&, \"\n            \"const surfaceScalarField&, transportModel&, const word&)\"\n        )   << \"Unknown turbulenceModel type \"\n            << modelType << nl << nl\n            << \"Valid turbulenceModel types:\" << endl\n            << turbulenceModelConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<turbulenceModel>\n    (\n        cstrIter()(U, phi, transport, turbulenceModelName)\n    );\n}\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nvoid turbulenceModel::correct()\n{\n    transportModel_.correct();\n\n    if (mesh_.changing())\n    {\n        y_.correct();\n    }\n}\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\n} // End namespace incompressible\n} // End namespace Foam\n```\n从基类 `turbulenceModel` 以下，就要花开两朵，各表一枝了。先来看 `RAS` 类的湍流模型。\n\n##### 1.2 RAS 模型\n我们从头文件 RASModel.H 看起\n```\nnamespace Foam\n{\nnamespace incompressible\n{\n\n/*---------------------------------------------------------------------------*\\\n                           Class RASModel Declaration\n\\*---------------------------------------------------------------------------*/\n\nclass RASModel\n:\n    public turbulenceModel, // RASModel 类是前面讲的turbulenceModel类的派生类\n    public IOdictionary // 同时也继承 IOdictionary 类\n{\n\nprotected:\n    \n        // 两个开关变量，类似于 C++ 中的bool变量，其值要么是true，要么是false。\n\t// 只是这里将 true 换成 on，false 换成 off 也是一样的。\n        Switch turbulence_;\n        Switch printCoeffs_;  \n\n        //- Model coefficients dictionary\n        dictionary coeffDict_;\n\n        //- Lower limit of k\n        dimensionedScalar kMin_;\n\n        //- Lower limit of epsilon\n        dimensionedScalar epsilonMin_;\n\n        //- Lower limit for omega\n        dimensionedScalar omegaMin_;\n\n    // Protected Member Functions\n\n        //- Print model coefficients\n        virtual void printCoeffs();\n\nprivate:\n    // Private Member Functions\n        //- Disallow default bitwise copy construct\n        RASModel(const RASModel&);\n        //- Disallow default bitwise assignment\n        void operator=(const RASModel&);\n        \npublic:\n\n    //- Runtime type information\n    TypeName(\"RASModel\");\n\n// 这里也涉及到 运行时选择机制，以后一起讲。\n        declareRunTimeSelectionTable\n        (\n            autoPtr,\n            RASModel,\n            dictionary,\n            (\n                const volVectorField& U,\n                const surfaceScalarField& phi,\n                transportModel& transport,\n                const word& turbulenceModelName\n            ),\n            (U, phi, transport, turbulenceModelName)\n        );\n\n        // 构造函数\n        RASModel\n        (\n            const word& type,\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = turbulenceModel::typeName\n        );\n\n        //- Return a reference to the selected RAS model 这个函数作为运行时选择机制里的选择器。\n        static autoPtr<RASModel> New\n        (\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = turbulenceModel::typeName\n        );\n\n\n    // Member Functions\n\n        // Access\n\n            // 注意，一下这些成员函数，是将来 RASModel 类的派生类们可能会用到的，这里一起定义了，\n\t    // 后面的派生类将可以直接继承这些函数，这样达到了代码重复利用的作用。\n            //- Return the lower allowable limit for k (default: SMALL)\n            const dimensionedScalar& kMin() const\n            {\n                return kMin_;\n            }\n\n            //- Return the lower allowable limit for epsilon (default: SMALL)\n            const dimensionedScalar& epsilonMin() const\n            {\n                return epsilonMin_;\n            }\n\n            //- Return the lower allowable limit for omega (default: SMALL)\n            const dimensionedScalar& omegaMin() const\n            {\n                return omegaMin_;\n            }\n\n            //- Allow kMin to be changed\n            dimensionedScalar& kMin()\n            {\n                return kMin_;\n            }\n\n            //- Allow epsilonMin to be changed\n            dimensionedScalar& epsilonMin()\n            {\n                return epsilonMin_;\n            }\n\n            //- Allow omegaMin to be changed\n            dimensionedScalar& omegaMin()\n            {\n                return omegaMin_;\n            }\n\n            //- Const access to the coefficients dictionary\n            virtual const dictionary& coeffDict() const\n            {\n                return coeffDict_;\n            }\n\n\n        //- Return the effective viscosity\n\t// 这个函数很重要，默认情况下，雷诺时均湍流模型中，有效粘度等于湍流粘度加层流粘度，即nut + nu。\n\t// 这里的 nut 和 nu 两个函数在基类 turbulenceModel 声明了，请往前翻以证实这一点。\n        virtual tmp<volScalarField> nuEff() const \n        {\n            return tmp<volScalarField>\n            (\n                new volScalarField(\"nuEff\", nut() + nu())\n            );\n        }\n\n        //- Solve the turbulence equations and correct the turbulence viscosity\n        virtual void correct();\n\n        //- Read RASProperties dictionary\n        virtual bool read();\n};\n\n} // End namespace incompressible\n} // End namespace Foam\n```\n\n再来看 `RASModel.C`，这里跟 `turbulenceModel.C ` 类似，重点关注构造函数和 选择器（Selector）函数。\n```\nnamespace Foam\n{\nnamespace incompressible\n{\n\ndefineTypeNameAndDebug(RASModel, 0);\ndefineRunTimeSelectionTable(RASModel, dictionary);\naddToRunTimeSelectionTable(turbulenceModel, RASModel, turbulenceModel);\n\n// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //\n\nvoid RASModel::printCoeffs()\n{\n    if (printCoeffs_)\n    {\n        Info<< type() << \"Coeffs\" << coeffDict_ << endl;\n    }\n}\n\n// 构造函数\nRASModel::RASModel\n(\n    const word& type,\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n:\n类似的，这里也是使用成员初始化列表来初始化当前类及其父类的数据成员\n\n    // 这一句是传递参数给父类 turbulenceModel，注意这里传给父类构造函数的参数要与父类中的构造函数的参数表一致哦\n    turbulenceModel(U, phi, transport, turbulenceModelName), \n    \n //这里是建立一个IOobject来初始化另一个父类IOdictionary，注意这里建立的是一个名为“RASProperties”的IO对象，这个文件相信用过 OpenFOAM 湍流模拟的一定很熟悉吧\n    IOdictionary\n    (\n        IOobject\n        (\n            \"RASProperties\",\n            U.time().constant(), // 这里表示文件的位置是在constant文件夹下\n            U.db(),\n            IOobject::MUST_READ_IF_MODIFIED, //这里的意思是，如果修改了，则需要重新读取，所以，如果你在算例运行时修改了这个文件，你的修改会即时生效的\n            IOobject::NO_WRITE\n        )\n    ),\n    \n  // 查找“turbulence” 关键字，并用其对应的值来初始化变量“turbulnce_”。\n    turbulence_(lookup(\"turbulence\")),\n    // 同上，区别是这里是带缺省参数的，也就是说如果找不到“printCoeffs” 就用缺省值“false”\n    printCoeffs_(lookupOrDefault<Switch>(\"printCoeffs\", false)), \n    \n    // 这里是读取模型参数字典来初始化变量“coeffDict_”。注意，这里的 type 是啥？这个要等到进一步看一个具体的湍流模型类时才能明了。 \n    // 另外，注意这里的 `lookup` ， `lookupOrDefault` 和 `subOrEmptyDict` 三个函数都是继承自 IOdictionary 类的函数。\n    coeffDict_(subOrEmptyDict(type + \"Coeffs\")), \n\n    kMin_(\"kMin\", sqr(dimVelocity), SMALL),\n    epsilonMin_(\"epsilonMin\", kMin_.dimensions()/dimTime, SMALL),\n    omegaMin_(\"omegaMin\", dimless/dimTime, SMALL)\n{\n\n    // 这里是从 RASProperties 里读取kMin等的值，注意如果 RASProperties 里不设置那就使用上面初始化的值“SMALL”。\n    // 注意这里的“*this”代表的是 RASModel 类本身，但是readIfPresent函数的参数应该是 dictionary 类，由于RASModel 类继承自 IODictionary 类，所以，这里其实隐含了一个派生类指针向基类指针的转换\n    kMin_.readIfPresent(*this);  \n    epsilonMin_.readIfPresent(*this);\n    omegaMin_.readIfPresent(*this);\n\n    // Force the construction of the mesh deltaCoeffs which may be needed\n    // for the construction of the derived models and BCs\n    mesh_.deltaCoeffs();\n}\n\n\n//这个也是跟运行时选择有关，以后会细说\nautoPtr<RASModel> RASModel::New\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n{\n    // get model name, but do not register the dictionary\n    // otherwise it is registered in the database twice\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                \"RASProperties\",\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).lookup(\"RASModel\")\n    );\n\n    Info<< \"Selecting RAS turbulence model \" << modelType << endl;\n\n    dictionaryConstructorTable::iterator cstrIter =\n        dictionaryConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == dictionaryConstructorTablePtr_->end())\n    {\n        FatalErrorIn\n        (\n            \"RASModel::New\"\n            \"(\"\n                \"const volVectorField&, \"\n                \"const surfaceScalarField&, \"\n                \"transportModel&, \"\n                \"const word&\"\n            \")\"\n        )   << \"Unknown RASModel type \"\n            << modelType << nl << nl\n            << \"Valid RASModel types:\" << endl\n            << dictionaryConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<RASModel>\n    (\n        cstrIter()(U, phi, transport, turbulenceModelName)\n    );\n}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nvoid RASModel::correct()\n{\n    turbulenceModel::correct();\n}\n\nbool RASModel::read()\n{\n    //if (regIOobject::read())\n\n    // Bit of trickery : we are both IOdictionary ('RASProperties') and\n    // an regIOobject from the turbulenceModel level. Problem is to distinguish\n    // between the two - we only want to reread the IOdictionary.\n\n    bool ok = IOdictionary::readData\n    (\n        IOdictionary::readStream\n        (\n            IOdictionary::type()\n        )\n    );\n    IOdictionary::close();\n\n    if (ok)\n    {\n        lookup(\"turbulence\") >> turbulence_;\n\n        if (const dictionary* dictPtr = subDictPtr(type() + \"Coeffs\"))\n        {\n            coeffDict_ <<= *dictPtr;\n        }\n        kMin_.readIfPresent(*this);\n        epsilonMin_.readIfPresent(*this);\n        omegaMin_.readIfPresent(*this);\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\n} // End namespace incompressible\n} // End namespace Foam\n```\n\n##### 1.3 LES 模型\nLESModel 类的结构与 RASModel 非常接近，所以这里就简单提一下区别之处。\n+ delta_ 成员\n```\n autoPtr<Foam::LESdelta> delta_;\n```\n这个成员是 LESdelta 类的对象，定义滤波尺度。这个类的定义在 `src/turbulenceModels/LES/LESdeltas` ，当中定义了几个可选的 delta 模型\n\n+ 亚格子粘度和应力\n\n```\n //- Return the SGS viscosity. 亚格子粘度\n        virtual tmp<volScalarField> nuSgs() const = 0;\n\n        //- Return the effective viscosity\n        virtual tmp<volScalarField> nuEff() const //有效粘度等于亚格子粘度与分子粘度之和\n        {\n            return tmp<volScalarField>\n            (\n                new volScalarField(\"nuEff\", nuSgs() + nu())\n            );\n        }\n        \n        //- Return the sub-grid stress tensor. // 亚格子应力\n        virtual tmp<volSymmTensorField> B() const = 0;\n```\nLESModel.C 的结构与 RASModel.C 几乎一样，所以这里就不重复了。\n\n前面提到，基类 turbulenceModel 里声明了很多纯虚函数，所以，turbulenceModel 类是抽象类，不能直接创建对象。注意这里的 RASModel 类和 LESModel 类由于继承了 turbulenceModel 类的纯虚函数，所以这两个依然是抽象类。这一点在后面解析具体湍流模型类的时候还会提到，这里先提个醒。\n\n##### 1.4 继承派生关系\n前面看完了基类 turbulenceModel，RASModel 以及 LESModel，可以看出这三个类的继承派生关系为：\n\n![RAS 和 LES 与 turbulenceModel 的继承关系](/image/turbulenceModel/RAS_LES.png)\n\n下面继续看具体湍流模型类与基类的继承关系。\n先看雷诺时均类湍流模型：\n位于 `src/turbulenceModels/incompressible/RAS` 目录下的都是雷诺时均类的湍流模型，这个类型的继承关系很简单：所有具体湍流模型类都继承自 RASModel 类，关系示意图如下：\n\n![RAS 类湍流模型的继承关系](/image/turbulenceModel/RAS.png)\n\n位于 `src/turbulenceModels/incompressible/LES` 目录下的是大涡模拟类型的湍流模型，这类湍流模型的继承关系比雷诺时均类的要复杂一点，但是也不难捋清，过程我就不详述了，下面给出我整理的继承关系图：\n\n![LES 类湍流模型的继承关系](/image/turbulenceModel/LES.png)\n \n上图中，虚线框里的是抽象类，实线框里的是具体的可以调用的湍流模型类。\n\n##### 1.5 求解器中湍流模型的调用\n最后简单提一下求解器中调用湍流模型的接口。以 `pisoFoam` 求解器为例：\n纵观 `pisoFoam` 的代码，跟湍流模型有关的共有三处，第一处是在 createField.H 的最后面，创建一个智能指针\n```\nautoPtr<incompressible::turbulenceModel> turbulence\n    (\n        incompressible::turbulenceModel::New(U, phi, laminarTransport)\n    );\n```\n注意，这个指针的类型是 `incompressible::turbulenceModel` ，也就是说，创建的是基类的指针。\n第二处位于 `UEqn` 中，\n```\n + turbulence->divDevReff(U)\n```\n从这里可以看出，这是用指针 `turbulence` 调用成员函数 `divDevReff` \n第三处在压力修正之后\n```\nturbulence->correct();\n```\n这里是调用成员函数 `correct` 。\n这简单的几行代码，就完成了湍流模型的调用。这里先大致说一下调用的原理和过程，详细的留待后面跟运行时选择机制一起说。\n首先，注意刚才提到的智能指针 `turbulence` 的类型是 `incompressible::turbulenceModel` ，是基类类型的。这里就不得不说一下 C++ **虚函数**的作用了。还记得上面提到的基类 `turbulenceModel` 中声明的那些**纯虚函数**吧，如果你往上翻翻，你会发现， `divDevReff` 和 `correct` 在基类 `turbulenceModel` 中都被声明为**纯虚函数**。这里只要把握两点，就能理解湍流模型的调用原理：\n1. 基类类型的指针可以指向派生类的对象；\n2. 在基类中声明的纯虚函数可以在派生类中进行定义，当基类类型指针指向派生类对象以后，用这个指针调用成员函数时，实际调用的是指针指向的派生类中定义的函数。\n\n把握这两点，然后再去理解湍流模型的调用过程：\n首先是调用 `incompressible::turbulenceModel::New` 函数来初始化指针 `turbulence` ，查看上面 `turbulenceModel` 类中 `New` 函数的定义，可以知道，函数要先从“turbulenceProperties” 文件里 `simulationType`　关键字，从而决定是调用 `RAS` 模型还是 `LES` 模型。如果用户设定的是 `RAS` ，那么 `turbulenceModel` 类的 `New` 函数将返回一个指向 `RASModel` 类的指针，这个指针将继续调用 `RASModel` 类的 `New` 函数，并在这个函数中读取 `RASProperties` 文件，查找关键字 `RASModel` ，从而决定具体调用的湍流模型。假设用户指定的是 `kEpsilon` 那么最终 createField.H 中定义的指针 `turbulence` 将指向一个 `kEpsilon` 类的对象，由此， `turbulence->divDevReff(U)` 和 `turbulence->correct()` 都将分别调用定义在 `kEpsilon` 类中的成员函数 `divDevReff` 和 `correct`。  \n一个简单的湍流模型调用示意图如下：\n\n![湍流模型调用示意图](/image/turbulenceModel/call.png)\n\n以上是 RAS 类型模型的调用，LES 类型的基本上差不多，但是，从上面的继承关系图也能看出，LES 模型类的结构更复杂一点。根本原因在于，LES 模型不像 RAS 模型那样都是同一的对湍流粘度 `nut` 建模，而是有一部分是对亚格子粘度 `nuSgs` 建模（这一部分湍流模型均继承自 `GenEddyVisc` ），还有另一部分是直接对亚格子应力 `B` 建模（这一部分湍流模型均继承自 `GenSGSStress` ），此外，分离涡模型（Detached eddy model， DESModel）也放在这个目录下，而且还有一个直接继承自 `LESModel` 的模型 `kOmegaSSTSAS` （这个模型与 `LESModel` 的关系就跟 `kEpsilon` 与 `RASModel` 的关系一样）。虽然有这么多种类型，但是调用过程其实跟 RAS 类型的是差不多的。如果从字典文件“turbulenceProperties” 里读到的 `simulationType` 是 `LES` 的话，那么将继续从字典文件 `LESProperties` 里读取具体的 LES 模型。上面总结的那张继承关系图中，所有的实线框中的模型都可以选择。但是，具体的模型需要具体的设置，比如，需要设置滤波尺度 `delta` 模型，还可能需要设置 `filter` 模型，具体的要求可以去具体的湍流模型类的代码中去看。如果你了解一点你需要使用的湍流模型的基本理论，能写出模型的方程，那要看懂这个湍流模型在 OpenFOAM 中实现的代码是很容易的。\n\n上述关于调用过程的叙述，只是我的理解，其实不严谨，但大致原理应该是这样。计划中这个系列将写三篇，我将在第三篇中叙述运行时选择机制，到时候还会深入说说这个调用过程。\n\n\nP.S：本系列在筹划时，OpenFOAM-3.0 版本还没发布。随着 3.0 版本的发布，本系列里对湍流模型的描述已经“过时”了，因为在 3.0 版中，湍流模型类进行了重新模板化，将单相湍流和多相湍流模型整合在一起了，所以这里的描述只适用于 3.0 以下的版本。\n","source":"_posts/OpenFOAM-singlePhase-turbulenceModel.md","raw":"title: \"OpenFOAM 中的单相流湍流模型之一\"\ndate: 2015-11-25 19:13:58\ncomments: true\ntags:\n - turbulence model\n - Code Explained\ncategories:\n - OpenFOAM\n---\n\n相信有不少 OpenFOAM 用户有添加湍流模型的需求，我自己最早用 OpenFOAM 完成的一项工作就是在其中添加了一些单相流的湍流模型，并进行了一些计算。这里将我对单相湍流模型代码框架的理解记录下来，供大家参考。本系列将包含三篇，第一篇介绍湍流模型类的继承派生关系，第二篇具体分析几个 OpenFOAM 中带的湍流模型，并给出修改或增加新模型的方法，第三篇分析湍流模型的运行时选择机制（Run Time Selection）的原理。\n\n<!--more-->\n\n#### 1. 湍流模型类的继承派生关系\n这一部分是最简单的，只要有一点`C++`的知识，看一下湍流模型的代码头文件的类声明部分，就能理解。OpenFOAM 里的单相湍流模型包含两大类，`RAS` 和 `LES`，下面将分别分析。\n\nOpenFOAM 单相流湍流模型的代码在 `src/turbulenceModels` 目录下，目录结构如下：\n```\n Allwmake  compressible  derivedFvPatchFields  incompressible  LES\n```\n其中， `compressible` 和 `incompressible` 分别是可压缩和不可压缩湍流模型的代码， `derivedFvPatchFields` 是两个湍流相关的边界条件的代码， `LES` 是大涡模拟的两个相关的类（ `LESdeltas` 和 `LESfilters` ），具体在后面会介绍。这里我主要关心不可压缩湍流模型。\n进入`incompressible`，目录结构为：\n```\n$  cd  incompressible\n$  ls\nAllwmake  LES  RAS  turbulenceModel\n```\n这里， 目录`turbulenceModel` 里是基类 `turbulenceModel` 相关的代码， `RAS` 和 `LES` ，顾名思义，分别是雷诺时均和大涡模拟湍流模型的代码。\n\n##### 1.1 基类 `turbulenceModel` \n首先看基类 `turbulenceModel` ，这里我挑着我觉得重要的部分代码列出来：\n\n先看头文件 turbulenceModel.H：\n```\nnamespace Foam\n{\n\n// incompressible 命名空间，注意这个是很重要的，作用是将类隔离开。比如，可压和不可压都有kEpsilon模型，这两个模型的类名是一样的。要区分这两个类，靠的就是这个命名空间。可压的是 compressible::kEpsilon, 而不可压的则是 incompressible::kEpsilon\nnamespace incompressible \n{\n\nclass turbulenceModel //定义一个 turbulenceModel 类，继承自 regIOobject 类\n:\n    public regIOobject\n{\n\nprotected:\n\n    // 数据成员\n        const Time& runTime_;\n        const fvMesh& mesh_;\n\n        const volVectorField& U_;\n        const surfaceScalarField& phi_;\n\n        transportModel& transportModel_; // 输运模型，涉及到分子粘度的设置\n\n        //- Near wall distance boundary field\n        nearWallDist y_;\n\npublic:\n\n    //- Runtime type information\n    TypeName(\"turbulenceModel\");\n\n\n    // Declare run-time New selection table 这个跟运行时选择有关，具体以后会涉及\n\n        declareRunTimeNewSelectionTable\n        (\n            autoPtr,\n            turbulenceModel,\n            turbulenceModel,\n            (\n                const volVectorField& U,\n                const surfaceScalarField& phi,\n                transportModel& transport,\n                const word& turbulenceModelName\n            ),\n            (U, phi, transport, turbulenceModelName)\n        );\n\n\n    // Constructors  构造函数\n\n        //- Construct from components\n        turbulenceModel\n        (\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = typeName\n        );\n\n\n    // Selectors // 这个也是跟运行时选择机制有关，涉及到具体湍流模型的选择过程\n        //- Return a reference to the selected turbulence model\n        static autoPtr<turbulenceModel> New\n        (\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = typeName\n        );\n\n\n    // Member Functions\n\n        //- Const access to the coefficients dictionary\n        virtual const dictionary& coeffDict() const = 0;\n\n        //- Helper function to return the nam eof the turbulence G field\n        inline word GName() const\n        {\n            return word(type() + \":G\");\n        }\n\n        //- Access function to velocity field\n        inline const volVectorField& U() const\n        {\n            return U_;\n        }\n\n        //- Access function to flux field\n        inline const surfaceScalarField& phi() const\n        {\n            return phi_;\n        }\n\n        //- Access function to incompressible transport model\n        inline transportModel& transport() const\n        {\n            return transportModel_;\n        }\n\n        //- Return the near wall distances\n        const nearWallDist& y() const\n        {\n            return y_;\n        }\n\n        //- Return the laminar viscosity\n\t// 分子粘度，注意这里的返回值是输运模型类对象的 nu 函数的返回值。\n\t// 具体来说，如果是牛顿流体，那么这个返回值就是我们在transportProperties里设置的粘度；\n\t// 如果是非牛顿流体，那么粘度是根据具体的非牛顿流体模型计算得到的。\n        inline tmp<volScalarField> nu() const \n        {\n            return transportModel_.nu();\n        }\n\n    // 以下形如 \"virtual xxxx () const = 0\" 的函数，都是纯虚函数，这是很重要的一部分。\n    // 当一个类中有纯虚函数时，这个类就被称作抽象类，抽象类本身不能建立对象，一般都是作为接口类来使用。\n    // 这里的turbulenceModel类就是接口类，“接口类”三个字的具体含义后面会解释。\n        //- Return the turbulence viscosity\n        virtual tmp<volScalarField> nut() const = 0;\n        //- Return the effective viscosity\n        virtual tmp<volScalarField> nuEff() const = 0;\n        //- Return the turbulence kinetic energy\n        virtual tmp<volScalarField> k() const = 0;\n        //- Return the turbulence kinetic energy dissipation rate\n        virtual tmp<volScalarField> epsilon() const = 0;\n        //- Return the Reynolds stress tensor\n        virtual tmp<volSymmTensorField> R() const = 0;\n        //- Return the effective stress tensor including the laminar stress\n        virtual tmp<volSymmTensorField> devReff() const = 0;\n        //- Return the source term for the momentum equation\n        virtual tmp<fvVectorMatrix> divDevReff(volVectorField& U) const = 0;\n        //- Return the source term for the momentum equation\n        virtual tmp<fvVectorMatrix> divDevRhoReff\n        (\n            const volScalarField& rho,\n            volVectorField& U\n        ) const = 0;\n        //- Solve the turbulence equations and correct the turbulence viscosity\n        virtual void correct() = 0;\n\n        //- Read LESProperties or RASProperties dictionary\n        virtual bool read() = 0;\n};\n```\n再来看 turbulenceModel.C，重点关注构造函数和 New 函数\n```\nnamespace Foam\n{\nnamespace incompressible\n{\n\n// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * \n\n//这句与运行时选择机制有关，后面再说\ndefineRunTimeSelectionTable(turbulenceModel, turbulenceModel);\n\n//构造函数的定义。构造函数包括四个参数，其中最后一个\"turbulenceModelName\"是带缺省参数的，所以，只需要提供三个参数。\nturbulenceModel::turbulenceModel\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n//  这里往下一段叫做成员初始化列表，用于对当前类以及其基类成员进行初始化\n:\n    regIOobject\n    (\n        IOobject\n        (\n            turbulenceModelName,\n            U.time().constant(),\n            U.db(),\n            IOobject::NO_READ,\n            IOobject::NO_WRITE\n        )\n    ),\n    runTime_(U.time()),\n    mesh_(U.mesh()),\n\n    U_(U),\n    phi_(phi),\n    transportModel_(transport), // 输运模型从构造函数的参数中读取\n    y_(mesh_)\n{}\n\n\n// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //\n\n这个函数，起着选择具体湍流模型的作用，后面我会结合求解器代码仔细说说这个函数。更详细的机制，将在结合运行时选择机制来解释。\nautoPtr<turbulenceModel> turbulenceModel::New\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n{\n    \n    // 注意这里了，算例中的\"turbulenceProperties\" 文件即由这段代码来读取。\n    // 需要从\"turbulenceProperties\" 文件中查找一个关键字\"simulationType\"，\n    // 然后将\"simulationType\"对应的值赋值给变量\"modelType\"（对于单相流，modelType 只可能是 \"RAS\" 或者 \"LES\"）。\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                \"turbulenceProperties\",\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).lookup(\"simulationType\")\n    );\n\n    Info<< \"Selecting turbulence model type \" << modelType << endl;\n\n    turbulenceModelConstructorTable::iterator cstrIter =\n        turbulenceModelConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == turbulenceModelConstructorTablePtr_->end())\n    {\n        FatalErrorIn\n        (\n            \"turbulenceModel::New(const volVectorField&, \"\n            \"const surfaceScalarField&, transportModel&, const word&)\"\n        )   << \"Unknown turbulenceModel type \"\n            << modelType << nl << nl\n            << \"Valid turbulenceModel types:\" << endl\n            << turbulenceModelConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<turbulenceModel>\n    (\n        cstrIter()(U, phi, transport, turbulenceModelName)\n    );\n}\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nvoid turbulenceModel::correct()\n{\n    transportModel_.correct();\n\n    if (mesh_.changing())\n    {\n        y_.correct();\n    }\n}\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\n} // End namespace incompressible\n} // End namespace Foam\n```\n从基类 `turbulenceModel` 以下，就要花开两朵，各表一枝了。先来看 `RAS` 类的湍流模型。\n\n##### 1.2 RAS 模型\n我们从头文件 RASModel.H 看起\n```\nnamespace Foam\n{\nnamespace incompressible\n{\n\n/*---------------------------------------------------------------------------*\\\n                           Class RASModel Declaration\n\\*---------------------------------------------------------------------------*/\n\nclass RASModel\n:\n    public turbulenceModel, // RASModel 类是前面讲的turbulenceModel类的派生类\n    public IOdictionary // 同时也继承 IOdictionary 类\n{\n\nprotected:\n    \n        // 两个开关变量，类似于 C++ 中的bool变量，其值要么是true，要么是false。\n\t// 只是这里将 true 换成 on，false 换成 off 也是一样的。\n        Switch turbulence_;\n        Switch printCoeffs_;  \n\n        //- Model coefficients dictionary\n        dictionary coeffDict_;\n\n        //- Lower limit of k\n        dimensionedScalar kMin_;\n\n        //- Lower limit of epsilon\n        dimensionedScalar epsilonMin_;\n\n        //- Lower limit for omega\n        dimensionedScalar omegaMin_;\n\n    // Protected Member Functions\n\n        //- Print model coefficients\n        virtual void printCoeffs();\n\nprivate:\n    // Private Member Functions\n        //- Disallow default bitwise copy construct\n        RASModel(const RASModel&);\n        //- Disallow default bitwise assignment\n        void operator=(const RASModel&);\n        \npublic:\n\n    //- Runtime type information\n    TypeName(\"RASModel\");\n\n// 这里也涉及到 运行时选择机制，以后一起讲。\n        declareRunTimeSelectionTable\n        (\n            autoPtr,\n            RASModel,\n            dictionary,\n            (\n                const volVectorField& U,\n                const surfaceScalarField& phi,\n                transportModel& transport,\n                const word& turbulenceModelName\n            ),\n            (U, phi, transport, turbulenceModelName)\n        );\n\n        // 构造函数\n        RASModel\n        (\n            const word& type,\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = turbulenceModel::typeName\n        );\n\n        //- Return a reference to the selected RAS model 这个函数作为运行时选择机制里的选择器。\n        static autoPtr<RASModel> New\n        (\n            const volVectorField& U,\n            const surfaceScalarField& phi,\n            transportModel& transport,\n            const word& turbulenceModelName = turbulenceModel::typeName\n        );\n\n\n    // Member Functions\n\n        // Access\n\n            // 注意，一下这些成员函数，是将来 RASModel 类的派生类们可能会用到的，这里一起定义了，\n\t    // 后面的派生类将可以直接继承这些函数，这样达到了代码重复利用的作用。\n            //- Return the lower allowable limit for k (default: SMALL)\n            const dimensionedScalar& kMin() const\n            {\n                return kMin_;\n            }\n\n            //- Return the lower allowable limit for epsilon (default: SMALL)\n            const dimensionedScalar& epsilonMin() const\n            {\n                return epsilonMin_;\n            }\n\n            //- Return the lower allowable limit for omega (default: SMALL)\n            const dimensionedScalar& omegaMin() const\n            {\n                return omegaMin_;\n            }\n\n            //- Allow kMin to be changed\n            dimensionedScalar& kMin()\n            {\n                return kMin_;\n            }\n\n            //- Allow epsilonMin to be changed\n            dimensionedScalar& epsilonMin()\n            {\n                return epsilonMin_;\n            }\n\n            //- Allow omegaMin to be changed\n            dimensionedScalar& omegaMin()\n            {\n                return omegaMin_;\n            }\n\n            //- Const access to the coefficients dictionary\n            virtual const dictionary& coeffDict() const\n            {\n                return coeffDict_;\n            }\n\n\n        //- Return the effective viscosity\n\t// 这个函数很重要，默认情况下，雷诺时均湍流模型中，有效粘度等于湍流粘度加层流粘度，即nut + nu。\n\t// 这里的 nut 和 nu 两个函数在基类 turbulenceModel 声明了，请往前翻以证实这一点。\n        virtual tmp<volScalarField> nuEff() const \n        {\n            return tmp<volScalarField>\n            (\n                new volScalarField(\"nuEff\", nut() + nu())\n            );\n        }\n\n        //- Solve the turbulence equations and correct the turbulence viscosity\n        virtual void correct();\n\n        //- Read RASProperties dictionary\n        virtual bool read();\n};\n\n} // End namespace incompressible\n} // End namespace Foam\n```\n\n再来看 `RASModel.C`，这里跟 `turbulenceModel.C ` 类似，重点关注构造函数和 选择器（Selector）函数。\n```\nnamespace Foam\n{\nnamespace incompressible\n{\n\ndefineTypeNameAndDebug(RASModel, 0);\ndefineRunTimeSelectionTable(RASModel, dictionary);\naddToRunTimeSelectionTable(turbulenceModel, RASModel, turbulenceModel);\n\n// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //\n\nvoid RASModel::printCoeffs()\n{\n    if (printCoeffs_)\n    {\n        Info<< type() << \"Coeffs\" << coeffDict_ << endl;\n    }\n}\n\n// 构造函数\nRASModel::RASModel\n(\n    const word& type,\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n:\n类似的，这里也是使用成员初始化列表来初始化当前类及其父类的数据成员\n\n    // 这一句是传递参数给父类 turbulenceModel，注意这里传给父类构造函数的参数要与父类中的构造函数的参数表一致哦\n    turbulenceModel(U, phi, transport, turbulenceModelName), \n    \n //这里是建立一个IOobject来初始化另一个父类IOdictionary，注意这里建立的是一个名为“RASProperties”的IO对象，这个文件相信用过 OpenFOAM 湍流模拟的一定很熟悉吧\n    IOdictionary\n    (\n        IOobject\n        (\n            \"RASProperties\",\n            U.time().constant(), // 这里表示文件的位置是在constant文件夹下\n            U.db(),\n            IOobject::MUST_READ_IF_MODIFIED, //这里的意思是，如果修改了，则需要重新读取，所以，如果你在算例运行时修改了这个文件，你的修改会即时生效的\n            IOobject::NO_WRITE\n        )\n    ),\n    \n  // 查找“turbulence” 关键字，并用其对应的值来初始化变量“turbulnce_”。\n    turbulence_(lookup(\"turbulence\")),\n    // 同上，区别是这里是带缺省参数的，也就是说如果找不到“printCoeffs” 就用缺省值“false”\n    printCoeffs_(lookupOrDefault<Switch>(\"printCoeffs\", false)), \n    \n    // 这里是读取模型参数字典来初始化变量“coeffDict_”。注意，这里的 type 是啥？这个要等到进一步看一个具体的湍流模型类时才能明了。 \n    // 另外，注意这里的 `lookup` ， `lookupOrDefault` 和 `subOrEmptyDict` 三个函数都是继承自 IOdictionary 类的函数。\n    coeffDict_(subOrEmptyDict(type + \"Coeffs\")), \n\n    kMin_(\"kMin\", sqr(dimVelocity), SMALL),\n    epsilonMin_(\"epsilonMin\", kMin_.dimensions()/dimTime, SMALL),\n    omegaMin_(\"omegaMin\", dimless/dimTime, SMALL)\n{\n\n    // 这里是从 RASProperties 里读取kMin等的值，注意如果 RASProperties 里不设置那就使用上面初始化的值“SMALL”。\n    // 注意这里的“*this”代表的是 RASModel 类本身，但是readIfPresent函数的参数应该是 dictionary 类，由于RASModel 类继承自 IODictionary 类，所以，这里其实隐含了一个派生类指针向基类指针的转换\n    kMin_.readIfPresent(*this);  \n    epsilonMin_.readIfPresent(*this);\n    omegaMin_.readIfPresent(*this);\n\n    // Force the construction of the mesh deltaCoeffs which may be needed\n    // for the construction of the derived models and BCs\n    mesh_.deltaCoeffs();\n}\n\n\n//这个也是跟运行时选择有关，以后会细说\nautoPtr<RASModel> RASModel::New\n(\n    const volVectorField& U,\n    const surfaceScalarField& phi,\n    transportModel& transport,\n    const word& turbulenceModelName\n)\n{\n    // get model name, but do not register the dictionary\n    // otherwise it is registered in the database twice\n    const word modelType\n    (\n        IOdictionary\n        (\n            IOobject\n            (\n                \"RASProperties\",\n                U.time().constant(),\n                U.db(),\n                IOobject::MUST_READ_IF_MODIFIED,\n                IOobject::NO_WRITE,\n                false\n            )\n        ).lookup(\"RASModel\")\n    );\n\n    Info<< \"Selecting RAS turbulence model \" << modelType << endl;\n\n    dictionaryConstructorTable::iterator cstrIter =\n        dictionaryConstructorTablePtr_->find(modelType);\n\n    if (cstrIter == dictionaryConstructorTablePtr_->end())\n    {\n        FatalErrorIn\n        (\n            \"RASModel::New\"\n            \"(\"\n                \"const volVectorField&, \"\n                \"const surfaceScalarField&, \"\n                \"transportModel&, \"\n                \"const word&\"\n            \")\"\n        )   << \"Unknown RASModel type \"\n            << modelType << nl << nl\n            << \"Valid RASModel types:\" << endl\n            << dictionaryConstructorTablePtr_->sortedToc()\n            << exit(FatalError);\n    }\n\n    return autoPtr<RASModel>\n    (\n        cstrIter()(U, phi, transport, turbulenceModelName)\n    );\n}\n\n\n// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //\n\nvoid RASModel::correct()\n{\n    turbulenceModel::correct();\n}\n\nbool RASModel::read()\n{\n    //if (regIOobject::read())\n\n    // Bit of trickery : we are both IOdictionary ('RASProperties') and\n    // an regIOobject from the turbulenceModel level. Problem is to distinguish\n    // between the two - we only want to reread the IOdictionary.\n\n    bool ok = IOdictionary::readData\n    (\n        IOdictionary::readStream\n        (\n            IOdictionary::type()\n        )\n    );\n    IOdictionary::close();\n\n    if (ok)\n    {\n        lookup(\"turbulence\") >> turbulence_;\n\n        if (const dictionary* dictPtr = subDictPtr(type() + \"Coeffs\"))\n        {\n            coeffDict_ <<= *dictPtr;\n        }\n        kMin_.readIfPresent(*this);\n        epsilonMin_.readIfPresent(*this);\n        omegaMin_.readIfPresent(*this);\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n\n} // End namespace incompressible\n} // End namespace Foam\n```\n\n##### 1.3 LES 模型\nLESModel 类的结构与 RASModel 非常接近，所以这里就简单提一下区别之处。\n+ delta_ 成员\n```\n autoPtr<Foam::LESdelta> delta_;\n```\n这个成员是 LESdelta 类的对象，定义滤波尺度。这个类的定义在 `src/turbulenceModels/LES/LESdeltas` ，当中定义了几个可选的 delta 模型\n\n+ 亚格子粘度和应力\n\n```\n //- Return the SGS viscosity. 亚格子粘度\n        virtual tmp<volScalarField> nuSgs() const = 0;\n\n        //- Return the effective viscosity\n        virtual tmp<volScalarField> nuEff() const //有效粘度等于亚格子粘度与分子粘度之和\n        {\n            return tmp<volScalarField>\n            (\n                new volScalarField(\"nuEff\", nuSgs() + nu())\n            );\n        }\n        \n        //- Return the sub-grid stress tensor. // 亚格子应力\n        virtual tmp<volSymmTensorField> B() const = 0;\n```\nLESModel.C 的结构与 RASModel.C 几乎一样，所以这里就不重复了。\n\n前面提到，基类 turbulenceModel 里声明了很多纯虚函数，所以，turbulenceModel 类是抽象类，不能直接创建对象。注意这里的 RASModel 类和 LESModel 类由于继承了 turbulenceModel 类的纯虚函数，所以这两个依然是抽象类。这一点在后面解析具体湍流模型类的时候还会提到，这里先提个醒。\n\n##### 1.4 继承派生关系\n前面看完了基类 turbulenceModel，RASModel 以及 LESModel，可以看出这三个类的继承派生关系为：\n\n![RAS 和 LES 与 turbulenceModel 的继承关系](/image/turbulenceModel/RAS_LES.png)\n\n下面继续看具体湍流模型类与基类的继承关系。\n先看雷诺时均类湍流模型：\n位于 `src/turbulenceModels/incompressible/RAS` 目录下的都是雷诺时均类的湍流模型，这个类型的继承关系很简单：所有具体湍流模型类都继承自 RASModel 类，关系示意图如下：\n\n![RAS 类湍流模型的继承关系](/image/turbulenceModel/RAS.png)\n\n位于 `src/turbulenceModels/incompressible/LES` 目录下的是大涡模拟类型的湍流模型，这类湍流模型的继承关系比雷诺时均类的要复杂一点，但是也不难捋清，过程我就不详述了，下面给出我整理的继承关系图：\n\n![LES 类湍流模型的继承关系](/image/turbulenceModel/LES.png)\n \n上图中，虚线框里的是抽象类，实线框里的是具体的可以调用的湍流模型类。\n\n##### 1.5 求解器中湍流模型的调用\n最后简单提一下求解器中调用湍流模型的接口。以 `pisoFoam` 求解器为例：\n纵观 `pisoFoam` 的代码，跟湍流模型有关的共有三处，第一处是在 createField.H 的最后面，创建一个智能指针\n```\nautoPtr<incompressible::turbulenceModel> turbulence\n    (\n        incompressible::turbulenceModel::New(U, phi, laminarTransport)\n    );\n```\n注意，这个指针的类型是 `incompressible::turbulenceModel` ，也就是说，创建的是基类的指针。\n第二处位于 `UEqn` 中，\n```\n + turbulence->divDevReff(U)\n```\n从这里可以看出，这是用指针 `turbulence` 调用成员函数 `divDevReff` \n第三处在压力修正之后\n```\nturbulence->correct();\n```\n这里是调用成员函数 `correct` 。\n这简单的几行代码，就完成了湍流模型的调用。这里先大致说一下调用的原理和过程，详细的留待后面跟运行时选择机制一起说。\n首先，注意刚才提到的智能指针 `turbulence` 的类型是 `incompressible::turbulenceModel` ，是基类类型的。这里就不得不说一下 C++ **虚函数**的作用了。还记得上面提到的基类 `turbulenceModel` 中声明的那些**纯虚函数**吧，如果你往上翻翻，你会发现， `divDevReff` 和 `correct` 在基类 `turbulenceModel` 中都被声明为**纯虚函数**。这里只要把握两点，就能理解湍流模型的调用原理：\n1. 基类类型的指针可以指向派生类的对象；\n2. 在基类中声明的纯虚函数可以在派生类中进行定义，当基类类型指针指向派生类对象以后，用这个指针调用成员函数时，实际调用的是指针指向的派生类中定义的函数。\n\n把握这两点，然后再去理解湍流模型的调用过程：\n首先是调用 `incompressible::turbulenceModel::New` 函数来初始化指针 `turbulence` ，查看上面 `turbulenceModel` 类中 `New` 函数的定义，可以知道，函数要先从“turbulenceProperties” 文件里 `simulationType`　关键字，从而决定是调用 `RAS` 模型还是 `LES` 模型。如果用户设定的是 `RAS` ，那么 `turbulenceModel` 类的 `New` 函数将返回一个指向 `RASModel` 类的指针，这个指针将继续调用 `RASModel` 类的 `New` 函数，并在这个函数中读取 `RASProperties` 文件，查找关键字 `RASModel` ，从而决定具体调用的湍流模型。假设用户指定的是 `kEpsilon` 那么最终 createField.H 中定义的指针 `turbulence` 将指向一个 `kEpsilon` 类的对象，由此， `turbulence->divDevReff(U)` 和 `turbulence->correct()` 都将分别调用定义在 `kEpsilon` 类中的成员函数 `divDevReff` 和 `correct`。  \n一个简单的湍流模型调用示意图如下：\n\n![湍流模型调用示意图](/image/turbulenceModel/call.png)\n\n以上是 RAS 类型模型的调用，LES 类型的基本上差不多，但是，从上面的继承关系图也能看出，LES 模型类的结构更复杂一点。根本原因在于，LES 模型不像 RAS 模型那样都是同一的对湍流粘度 `nut` 建模，而是有一部分是对亚格子粘度 `nuSgs` 建模（这一部分湍流模型均继承自 `GenEddyVisc` ），还有另一部分是直接对亚格子应力 `B` 建模（这一部分湍流模型均继承自 `GenSGSStress` ），此外，分离涡模型（Detached eddy model， DESModel）也放在这个目录下，而且还有一个直接继承自 `LESModel` 的模型 `kOmegaSSTSAS` （这个模型与 `LESModel` 的关系就跟 `kEpsilon` 与 `RASModel` 的关系一样）。虽然有这么多种类型，但是调用过程其实跟 RAS 类型的是差不多的。如果从字典文件“turbulenceProperties” 里读到的 `simulationType` 是 `LES` 的话，那么将继续从字典文件 `LESProperties` 里读取具体的 LES 模型。上面总结的那张继承关系图中，所有的实线框中的模型都可以选择。但是，具体的模型需要具体的设置，比如，需要设置滤波尺度 `delta` 模型，还可能需要设置 `filter` 模型，具体的要求可以去具体的湍流模型类的代码中去看。如果你了解一点你需要使用的湍流模型的基本理论，能写出模型的方程，那要看懂这个湍流模型在 OpenFOAM 中实现的代码是很容易的。\n\n上述关于调用过程的叙述，只是我的理解，其实不严谨，但大致原理应该是这样。计划中这个系列将写三篇，我将在第三篇中叙述运行时选择机制，到时候还会深入说说这个调用过程。\n\n\nP.S：本系列在筹划时，OpenFOAM-3.0 版本还没发布。随着 3.0 版本的发布，本系列里对湍流模型的描述已经“过时”了，因为在 3.0 版中，湍流模型类进行了重新模板化，将单相湍流和多相湍流模型整合在一起了，所以这里的描述只适用于 3.0 以下的版本。\n","slug":"OpenFOAM-singlePhase-turbulenceModel","published":1,"updated":"2016-03-12T08:11:29.843Z","layout":"post","photos":[],"link":"","_id":"cioiqegfh004jz8mbhpqtxe47"},{"title":"在 Windows 7 下配置 OpenFOAM 运行环境","date":"2015-06-14T13:06:32.000Z","comments":1,"_content":"\n本篇介绍如何在 Windows 7 64 bit 下配置 OpenFOAM 的编译及运行环境。我并不打算在 windows 下进行 OpenFOAM 的开发，这里的折腾仅仅是作为一种测试，想看看目前的 OpenFOAM for windows 安装能做到什么程度。我使用的是[Creative Fields](http://www.c-fields.com/)提供的安装包，编译环境是基于 Mingw-64 来搭建的，linux模拟环境采用的是 MSYS，终端使用的是 [mintty](https://code.google.com/p/mintty/)。经过一番折腾，我成功在 Windows 下安装了OpenFOAM-2.3.0，程序可以串行或并行运行，也可以用 wmake 编译新的代码。本篇博文不会一步一步详细介绍我的搭建过程，仅介绍一些基本的原则以及我遇到的坑。尤其注意的是，以下很多步骤其实对于安装 OpenFOAM for Windows 不是必需的，仅仅是因为我的特殊要求而徒增折腾而已。\n\n<!--more-->\n\n## 1. 安装\n安装步骤其实很简单，从[Creative Fields](http://www.c-fields.com/technical-area/downloads)下载 OpenFOAM for Windows，然后运行安装程序，选择需要的安装的 OpenFOAM 版本（OpenFOAM-2.3.0 或者 FOAM-extend-3.1，这里我安装的是前者），此外 Mingw compiler 也需要选上，不然无法编译新的 OpenFOAM 代码。选好以后，就开始安装过程，安装过程其实是将预编译好的 OpenFOAM 解压到你指定的目录。安装结束以后，从`install_dir/OpenFOAM\\OpenFOAM-2.3.0`运行`of23.bat`,便会开启一个 Windows 的 cmd 窗口，从这个窗口里便可以运行 OpenFOAM 了。经测试，串行运行 `cavity` 和并行运行 `dambreak` 算例都能成功。如果仅仅满足于能在 Windows 下运行 OpenFOAM，那到这里就可以了，下面的不需要再看了。\n\n但是，默认的 OpenFOAM 运行环境有很多不如意的地方，一是终端太简陋，没有颜色；二是 `vim` 使用的体验非常差；三是无法正常编译新的 OpenFOAM 代码。为了获得更好的使用体验以及更完整的功能，我进行了以下折腾。\n\n## 2. 运行环境的配置\n先简单描述一下我电脑的配置环境：我已安装过 32位版的 mingw，并配置好了 msys+mintty 环境，使用体验很不错。但是，从[Creative Fields](http://www.c-fields.com/technical-area/downloads) 提供的 OpenFOAM for Windows 默认使用的是它自带的 MSYS，经测试有问题，最不能忍的一个是，这个版本的 MSYS 配合 mintty 终端使用的时候，无法使用`ctrl + z`将程序放到后台，按`ctrl + z`会导致终端崩溃。此外，这个版本的 OpenFOAM 是在64 环境编译好的，经测验无法在 32位mingw 环境下编译新的代码，因为库的版本不一致。我不想破坏原来的配置，希望实现两个不同的终端入口，其中一个进入后会自动载入OpenFOAM的运行环境，包括 OpenFOAM 相关的环境变量， Mingw-64 编译环境，以及我配置好的 msys+mintty 运行环境。另一个入口，进入后则会自动载入先前配置的 msys + mingw-32 运行环境。下面开始配置：\n#### 2.1. 修改`of23.bat`\n默认的内容如下：\n```\n@echo OFF\nif NOT exist D:\\OpenFOAM\\MSYS\\home\\%USERNAME%\\.profile call D:\\OpenFOAM\\MSYS\\initProfile.bat\nSET FOAM=of23\nD:\\OpenFOAM\\MSYS\\bin\\sh.exe --login -i\n\n```\n我想载入我的 msys 环境，则需要对这个批处理脚本进行修改。最省事的方法是将原来安装 msys 时的脚本`msys.bat`的内容拷过来，然后加上一些 OpemFOAM 相关的变量，经测试，只需要改三处：\n+ 加上这一句`if NOT exist D:\\OpenFOAM\\MSYS\\home\\%USERNAME%\\.profile call D:\\OpenFOAM\\MSYS\\initProfile.bat`，这个跟加载 OpenFOAM 环境变量有关。\n+ 加一个环境变量 `set FOAM=of23`，注意如果安装的是`FOAM-extend-3.1`这一条可能会不一样。\n+ 指定 msys 的路径，一共有两处需要修改。\n`if NOT EXIST %WD%msys-1.0.dll set WD=.\\bin\\` 改成\n `if NOT EXIST %WD%msys-1.0.dll set WD=C:\\MinGw\\msys\\1.0\\bin\\`\n 以及\n`if NOT EXIST %WD%msys-1.0.dll set WD=%~dp0\\bin\\` 改成\n`if NOT EXIST %WD%msys-1.0.dll set WD=C:\\MinGw\\msys\\1.0\\bin\\`\n注意`C:\\MinGw\\msys\\1.0\\bin\\`是我的 msys 的路径。\n\n 完成这个以后，建立一个`of23.bat`的快捷方式，并将快捷方式的属性里目标设为:`D:\\OpenFOAM\\OpenFOAM\\OpenFOAM-2.3.0\\of23.bat -mintty`。设置好以后，运行这个快捷方式，便会自动载入 msys+mintty 运行环境了，并且自动配置好了OpenFOAM运行相关的环境。\n\n#### 2.2. 解决32位和64位 Mingw 的冲突问题\n以上运行环境里默认加载的是32位 Mingw 编译环境，无法编译新的 OpenFOAM 代码，为了解决这个问题，需要实现在需要运行 OpenFOAM 时，自动载入64位的 Mingw，这需要通过修改`$HOME/.profile` 文件来实现。摸索过程不细说了，这里贴上我的最终配置：\n```\nif [ -n \"${FOAM+1}\" ]; then  \nif [ $FOAM == \"of23\" ]; then  \necho Setting up OpenFoam 2.3 shell  \nsource /D/OpenFOAM/OpenFOAM/OpenFOAM-2.3.0/etc/bashrc  \nexport PATH=/D/OpenFOAM/MinGW/bin:$PATH\nexport CPLUS_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport C_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport LIBRARY_PATH='D:\\OpenFOAM\\MinGW\\lib;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2'\nelif [ $FOAM == \"f3\" ]; then \necho Setting up foam-extend-3.1 shell  \nsource /D/OpenFOAM/OpenFOAM/foam-extend-3.1/etc/bashrc  \nexport PATH=/D/OpenFOAM/MinGW/bin:$PATH\nexport CPLUS_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport C_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport LIBRARY_PATH='D:\\OpenFOAM\\MinGW\\lib;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2'\nelse  \necho No foam preset in MSYS  \nfi  \nfi  \n#echo Setting up ParaView \nexport PATH=$PATH:/d/Program\\ Files\\ \\(x86\\)//ParaView\\ 4.1.0/bin/\n# echo Setting up PATH \nexport PATH=$LD_LIBRARY_PATH:$PATH \n# echo Done \n```\n以上配置好以后，便可以用`wmake`来编译新的 OpenFOAM 代码了。\n\n#### 2.3. 一些坑\n如果以上配置好以后，仍然无法编译你的 OpenFOAM 代码，那可能是以下原因造成的：\n+ Creative Fields 这个版本的 OpenFOAM，将所有的属于 OpenFOAM 的源码文件名都在之前加了一个`OF_`的前缀。我估计这是为了防止文件名冲突，因为windows下，文件名不区分大小写，如此一来，OpenFOAM的源码文件`Vector.H`便与标准C++的头文件`vector.h`无法区分了。所以，从Linux下拷贝过来的OpenFOAM源码无法直接在这个环境下编译，需要修改一下`#include`的源文件名再尝试。\n+ wmake编译的时候，默认会去`src`目录下编译生成的`lnInclude`目录下寻找头文件，但是，估计是由于 msys 环境下不支持符号链接，这个版本的OpenFOAM的 `src`目录下不自带`lnInclude`目录，所以，编译会遇到找不到头文件的错误，解决办法是重新运行一次`src`目录的`Allwmake`，编译不会成功，不过无所谓，只要生成了那些`lnInclude`目录就行了。\n+ mpicc，mpic++，mpicxx这几个程序在默认情况下不能正常运行，原因是，在`D:\\OpenFOAM\\OpenFOAM\\ThirdParty-2.3.0\\platforms\\mingw64Gcc\\openmpi-1.6.5\\share\\openmpi`下定义的`*-wrapper-data.txt`文件中，指定的默认编译器是`cl.exe`，这是`visual studio`带的命令行编译器。将之改成`gcc.exe`( mpic++ 和 mpicxx 的改成`g++.exe`)，便可以正常运行了。\n\n最后，所有配置都弄好以后，便可以以比较好的体验在Windows下编译和运行OpenFOAM了，但是，msys+mintty 这套环境仍有一个遗憾，那就是并行不能正常。如果在 mintty 终端里直接运行`mpirun -np 4 interFoam.exe -parallel`，会报错：`..\\..\\..\\SOURCES\\openmpi-1.6.5\\opal\\event\\event.c: ompi_evesel->dispatch() failed.`。但是如果用 `nohup` 命令，`nohup mpirun -np 4 interFoam.exe -parallel &`则可以并行运行。只是仍有一个bug，那就是程序运行结束以后，进程`interFoam.exe`不会自动关闭，需要手动从任务管理器里去结束。但在默认的`of23.bat`环境里，并行是正常的，所以妥协的办法是当需要并行运行的时候，就转移到默认环境里去运行。\n\n以上仅供读者参考，不推荐新手去折腾，因为不太值得，安装一个 Ubuntu 虚拟机，然后在里面安装配置 OpenFOAM 简单多了，而且使用体验也更好。即便要折腾，也不太推荐Creative Fields 的这个安装包，因为它在所有的OpenFOAM源文件名前加了`OF_`前缀,这对编译从linux环境下拷贝过来的 OpenFOAM 代码造成了不小的麻烦。不知道其他的OpenFOAM for windows 安装包采用的是什么方法来规避上面提到的文件名可能冲突这个问题的。\n\n顺带提一句，Creative Fields 除了提供OpenFOAM for Windows，还有一个画网格工具套装叫`cfMesh`，经简单试用，其特点是自动化程度很高，网格生成速度特别快。\n\n","source":"_posts/OpenFOAM-on-win.md","raw":"title: \"在 Windows 7 下配置 OpenFOAM 运行环境\"\ndate: 2015-06-14 21:06:32\ncomments: true\ntags:\n - OpenFOAM\n - Windows\ncategories:\n - OpenFOAM\n---\n\n本篇介绍如何在 Windows 7 64 bit 下配置 OpenFOAM 的编译及运行环境。我并不打算在 windows 下进行 OpenFOAM 的开发，这里的折腾仅仅是作为一种测试，想看看目前的 OpenFOAM for windows 安装能做到什么程度。我使用的是[Creative Fields](http://www.c-fields.com/)提供的安装包，编译环境是基于 Mingw-64 来搭建的，linux模拟环境采用的是 MSYS，终端使用的是 [mintty](https://code.google.com/p/mintty/)。经过一番折腾，我成功在 Windows 下安装了OpenFOAM-2.3.0，程序可以串行或并行运行，也可以用 wmake 编译新的代码。本篇博文不会一步一步详细介绍我的搭建过程，仅介绍一些基本的原则以及我遇到的坑。尤其注意的是，以下很多步骤其实对于安装 OpenFOAM for Windows 不是必需的，仅仅是因为我的特殊要求而徒增折腾而已。\n\n<!--more-->\n\n## 1. 安装\n安装步骤其实很简单，从[Creative Fields](http://www.c-fields.com/technical-area/downloads)下载 OpenFOAM for Windows，然后运行安装程序，选择需要的安装的 OpenFOAM 版本（OpenFOAM-2.3.0 或者 FOAM-extend-3.1，这里我安装的是前者），此外 Mingw compiler 也需要选上，不然无法编译新的 OpenFOAM 代码。选好以后，就开始安装过程，安装过程其实是将预编译好的 OpenFOAM 解压到你指定的目录。安装结束以后，从`install_dir/OpenFOAM\\OpenFOAM-2.3.0`运行`of23.bat`,便会开启一个 Windows 的 cmd 窗口，从这个窗口里便可以运行 OpenFOAM 了。经测试，串行运行 `cavity` 和并行运行 `dambreak` 算例都能成功。如果仅仅满足于能在 Windows 下运行 OpenFOAM，那到这里就可以了，下面的不需要再看了。\n\n但是，默认的 OpenFOAM 运行环境有很多不如意的地方，一是终端太简陋，没有颜色；二是 `vim` 使用的体验非常差；三是无法正常编译新的 OpenFOAM 代码。为了获得更好的使用体验以及更完整的功能，我进行了以下折腾。\n\n## 2. 运行环境的配置\n先简单描述一下我电脑的配置环境：我已安装过 32位版的 mingw，并配置好了 msys+mintty 环境，使用体验很不错。但是，从[Creative Fields](http://www.c-fields.com/technical-area/downloads) 提供的 OpenFOAM for Windows 默认使用的是它自带的 MSYS，经测试有问题，最不能忍的一个是，这个版本的 MSYS 配合 mintty 终端使用的时候，无法使用`ctrl + z`将程序放到后台，按`ctrl + z`会导致终端崩溃。此外，这个版本的 OpenFOAM 是在64 环境编译好的，经测验无法在 32位mingw 环境下编译新的代码，因为库的版本不一致。我不想破坏原来的配置，希望实现两个不同的终端入口，其中一个进入后会自动载入OpenFOAM的运行环境，包括 OpenFOAM 相关的环境变量， Mingw-64 编译环境，以及我配置好的 msys+mintty 运行环境。另一个入口，进入后则会自动载入先前配置的 msys + mingw-32 运行环境。下面开始配置：\n#### 2.1. 修改`of23.bat`\n默认的内容如下：\n```\n@echo OFF\nif NOT exist D:\\OpenFOAM\\MSYS\\home\\%USERNAME%\\.profile call D:\\OpenFOAM\\MSYS\\initProfile.bat\nSET FOAM=of23\nD:\\OpenFOAM\\MSYS\\bin\\sh.exe --login -i\n\n```\n我想载入我的 msys 环境，则需要对这个批处理脚本进行修改。最省事的方法是将原来安装 msys 时的脚本`msys.bat`的内容拷过来，然后加上一些 OpemFOAM 相关的变量，经测试，只需要改三处：\n+ 加上这一句`if NOT exist D:\\OpenFOAM\\MSYS\\home\\%USERNAME%\\.profile call D:\\OpenFOAM\\MSYS\\initProfile.bat`，这个跟加载 OpenFOAM 环境变量有关。\n+ 加一个环境变量 `set FOAM=of23`，注意如果安装的是`FOAM-extend-3.1`这一条可能会不一样。\n+ 指定 msys 的路径，一共有两处需要修改。\n`if NOT EXIST %WD%msys-1.0.dll set WD=.\\bin\\` 改成\n `if NOT EXIST %WD%msys-1.0.dll set WD=C:\\MinGw\\msys\\1.0\\bin\\`\n 以及\n`if NOT EXIST %WD%msys-1.0.dll set WD=%~dp0\\bin\\` 改成\n`if NOT EXIST %WD%msys-1.0.dll set WD=C:\\MinGw\\msys\\1.0\\bin\\`\n注意`C:\\MinGw\\msys\\1.0\\bin\\`是我的 msys 的路径。\n\n 完成这个以后，建立一个`of23.bat`的快捷方式，并将快捷方式的属性里目标设为:`D:\\OpenFOAM\\OpenFOAM\\OpenFOAM-2.3.0\\of23.bat -mintty`。设置好以后，运行这个快捷方式，便会自动载入 msys+mintty 运行环境了，并且自动配置好了OpenFOAM运行相关的环境。\n\n#### 2.2. 解决32位和64位 Mingw 的冲突问题\n以上运行环境里默认加载的是32位 Mingw 编译环境，无法编译新的 OpenFOAM 代码，为了解决这个问题，需要实现在需要运行 OpenFOAM 时，自动载入64位的 Mingw，这需要通过修改`$HOME/.profile` 文件来实现。摸索过程不细说了，这里贴上我的最终配置：\n```\nif [ -n \"${FOAM+1}\" ]; then  \nif [ $FOAM == \"of23\" ]; then  \necho Setting up OpenFoam 2.3 shell  \nsource /D/OpenFOAM/OpenFOAM/OpenFOAM-2.3.0/etc/bashrc  \nexport PATH=/D/OpenFOAM/MinGW/bin:$PATH\nexport CPLUS_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport C_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport LIBRARY_PATH='D:\\OpenFOAM\\MinGW\\lib;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2'\nelif [ $FOAM == \"f3\" ]; then \necho Setting up foam-extend-3.1 shell  \nsource /D/OpenFOAM/OpenFOAM/foam-extend-3.1/etc/bashrc  \nexport PATH=/D/OpenFOAM/MinGW/bin:$PATH\nexport CPLUS_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport C_INCLUDE_PATH='D:\\OpenFOAM\\MinGw\\include;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2\\include'\nexport LIBRARY_PATH='D:\\OpenFOAM\\MinGW\\lib;D:\\OpenFOAM\\MinGW\\lib\\gcc\\x86_64-w64-mingw32\\4.9.2'\nelse  \necho No foam preset in MSYS  \nfi  \nfi  \n#echo Setting up ParaView \nexport PATH=$PATH:/d/Program\\ Files\\ \\(x86\\)//ParaView\\ 4.1.0/bin/\n# echo Setting up PATH \nexport PATH=$LD_LIBRARY_PATH:$PATH \n# echo Done \n```\n以上配置好以后，便可以用`wmake`来编译新的 OpenFOAM 代码了。\n\n#### 2.3. 一些坑\n如果以上配置好以后，仍然无法编译你的 OpenFOAM 代码，那可能是以下原因造成的：\n+ Creative Fields 这个版本的 OpenFOAM，将所有的属于 OpenFOAM 的源码文件名都在之前加了一个`OF_`的前缀。我估计这是为了防止文件名冲突，因为windows下，文件名不区分大小写，如此一来，OpenFOAM的源码文件`Vector.H`便与标准C++的头文件`vector.h`无法区分了。所以，从Linux下拷贝过来的OpenFOAM源码无法直接在这个环境下编译，需要修改一下`#include`的源文件名再尝试。\n+ wmake编译的时候，默认会去`src`目录下编译生成的`lnInclude`目录下寻找头文件，但是，估计是由于 msys 环境下不支持符号链接，这个版本的OpenFOAM的 `src`目录下不自带`lnInclude`目录，所以，编译会遇到找不到头文件的错误，解决办法是重新运行一次`src`目录的`Allwmake`，编译不会成功，不过无所谓，只要生成了那些`lnInclude`目录就行了。\n+ mpicc，mpic++，mpicxx这几个程序在默认情况下不能正常运行，原因是，在`D:\\OpenFOAM\\OpenFOAM\\ThirdParty-2.3.0\\platforms\\mingw64Gcc\\openmpi-1.6.5\\share\\openmpi`下定义的`*-wrapper-data.txt`文件中，指定的默认编译器是`cl.exe`，这是`visual studio`带的命令行编译器。将之改成`gcc.exe`( mpic++ 和 mpicxx 的改成`g++.exe`)，便可以正常运行了。\n\n最后，所有配置都弄好以后，便可以以比较好的体验在Windows下编译和运行OpenFOAM了，但是，msys+mintty 这套环境仍有一个遗憾，那就是并行不能正常。如果在 mintty 终端里直接运行`mpirun -np 4 interFoam.exe -parallel`，会报错：`..\\..\\..\\SOURCES\\openmpi-1.6.5\\opal\\event\\event.c: ompi_evesel->dispatch() failed.`。但是如果用 `nohup` 命令，`nohup mpirun -np 4 interFoam.exe -parallel &`则可以并行运行。只是仍有一个bug，那就是程序运行结束以后，进程`interFoam.exe`不会自动关闭，需要手动从任务管理器里去结束。但在默认的`of23.bat`环境里，并行是正常的，所以妥协的办法是当需要并行运行的时候，就转移到默认环境里去运行。\n\n以上仅供读者参考，不推荐新手去折腾，因为不太值得，安装一个 Ubuntu 虚拟机，然后在里面安装配置 OpenFOAM 简单多了，而且使用体验也更好。即便要折腾，也不太推荐Creative Fields 的这个安装包，因为它在所有的OpenFOAM源文件名前加了`OF_`前缀,这对编译从linux环境下拷贝过来的 OpenFOAM 代码造成了不小的麻烦。不知道其他的OpenFOAM for windows 安装包采用的是什么方法来规避上面提到的文件名可能冲突这个问题的。\n\n顺带提一句，Creative Fields 除了提供OpenFOAM for Windows，还有一个画网格工具套装叫`cfMesh`，经简单试用，其特点是自动化程度很高，网格生成速度特别快。\n\n","slug":"OpenFOAM-on-win","published":1,"updated":"2015-06-17T06:25:43.426Z","layout":"post","photos":[],"link":"","_id":"cioiqegfl004nz8mbkvoa8a40"},{"title":"在 CentOS 上安装 OpenFOAM ","date":"2015-09-13T05:23:06.000Z","comments":1,"_content":"\n本篇记录我在 CentOS 上编译安装 OpenFOAM 的过程。我需要在不能联网且没有 root 权限的集群上使用OpenFOAM ，最早的时候，我使用的是 [centFOAM project](http://sourceforge.net/projects/centfoam/) 提供的 64bit CentOS 安装包，这个很方便，把压缩包下载，解压，然后配置一下环境变量就可以了。但是后来 centFOAM 好像不再更新了，所以我只好尝试着自己编译。\n\n由于集群上缺少一些 OpenFOAM 依赖的包，要是一个一个去下载源码编译以补齐那些依赖的包，实在很费劲，所以我采取了另一种尝试：在虚拟机里安装跟集群上一样的系统，然后在虚拟机里编译好 OpenFOAM ，再拷贝到集群上去用（据信 cenoFOAM project 提供的安装包也是在虚拟机编译好的，而且在 cfd-online 论坛上也见有人推荐这么做）。我尝试过在 CentOS 5.4 ，CentOS 6.3 以及 Scientific Linux 6.5 上安装过 OpenFOAM-2.3.x，都成功了，过程大同小异。下面是我的安装过程的一个简要记录。\n\n<!--more-->\n\n### 1 CentOS 6.3\n首先，你需要一个虚拟机软件，我使用的是 VirtualBox，然后在虚拟机里安装一个64 bit 的CentOS 6.3（镜像可以去[官网](http://vault.centos.org/)下载）。注意，要想在 VirtualBox安装 64 bit的虚拟机，你的主系统也必须是 64 bit，而且，还要求你的 CPU 支持并开启了虚拟化技术，见[这个链接](http://rickie622.blog.163.com/blog/static/2123881120113473224536/)。然后，安装虚拟机的过程中，CentOS 6.3 会让你选择以哪种方式安装系统，我选择的是 \"Software Development Workstation\"，因为这种方式安装的包最全。以下是我在虚拟机里编译安装 OpenFOAM-2.3.x 的过程。\n\n\n#### 1.0 补充一些依赖包\n确保你的虚拟机可以联网，然后运行以下命令补充一些包：\n```\nyum groupinstall 'Development Tools'\n\nyum install glibc-devel.i686\n\nyum install zlib.x86_64\nyum install zlib-devel.x86_64\n```\n\n\n#### 1.1 下载需要的源码包。\n需要的源码包包括：OpenFOAM-2.3.x ，ThirdParty-2.3.x，这两个可以在OpenFOAM的官网找到，其中 OpenFOAM-2.3.x 可以用 git 从[这里](https://github.com/OpenFOAM/OpenFOAM-2.3.x)克隆一份；gcc-4.8.2, mpfr-3.1.2, gmp-5.1.2, mpc-1.0.1, boost-1.55.0, 这些可以OpenFOAM的 github 仓库里[找到链接](https://github.com/OpenFOAM/ThirdParty-2.3.x)。因为有移植到集群上的需要，所以我这里需要自己编译gcc 和openmpi，这样，就不需要依赖集群系统上的 gcc 和 openmpi 了。\n\n#### 1.2 将源码包解压到合适的位置\n建议在虚拟里，创建一个普通用户（假设为 user），不要用root用户来编译，以下操作都假定是以user用户的身份在进行。在 $HOME 下创建一个目录 OpenFOAM，然后将OpenFOAM-2.3.x 以及 ThirdParty-2.3.x 拷贝到该目录下（如果需要的话，先解压）。然后，解压 gcc-4.8.2，mpfr-3.1.2, gmp-5.1.2, mpc-1.0.1, boost-1.55.0, 并拷贝到 `$HOME/ThirdParty-2.3.x` 下，并将 boost-1.55.0 重命名为 boost-system。\n\n#### 1.3 编译前的配置和检查\n打开`$HOME/OpenFOAM/OpenFOAM-2.3.x/etc/config/settings.sh` 文件，跳到\n```\ncase \"${foamCompiler}\" in\nOpenFOAM | ThirdParty)\n    case \"$WM_COMPILER\" in\n    Gcc | Gcc++0x | Gcc48 | Gcc48++0x)\n        gcc_version=gcc-4.8.2\n        gmp_version=gmp-5.1.2\n        mpfr_version=mpfr-3.1.2\n        mpc_version=mpc-1.0.1\n        ;;\n```\n检查一下你下载的软件包版本跟这里的设置是否一样，如果不一致，可以直接修改这个文件以使这里的设置和你下载的版本一致。\n此外，还需要跳到\n```\nOPENMPI)\n    export FOAM_MPI=openmpi-1.6.5\n    # optional configuration tweaks:\n    _foamSource `$WM_PROJECT_DIR/bin/foamEtcFile config/openmpi.sh`\n\n    export MPI_ARCH_PATH=$WM_THIRD_PARTY_DIR/platforms/$WM_ARCH$WM_COMPILER/$FOAM_MPI\n\n    # Tell OpenMPI where to find its install directory\n    export OPAL_PREFIX=$MPI_ARCH_PATH\n\n    _foamAddPath    $MPI_ARCH_PATH/bin\n\n    # 64-bit on OpenSuSE 12.1 uses lib64 others use lib\n    _foamAddLib     $MPI_ARCH_PATH/lib$WM_COMPILER_LIB_ARCH\n    _foamAddLib     $MPI_ARCH_PATH/lib\n\n    _foamAddMan     $MPI_ARCH_PATH/share/man\n    ;;\n\n```\n看看你的 ThirdParty-2.3.x 下的 openmpi 的版本是否跟这里的一样（上面的设置是 openmpi-1.6.5），如果不一样，也需要修改这个文件。\n\n然后，打开`$HOME/OpenFOAM/OpenFOAM-2.3.x/etc/bashrc` 文件，设置\n```\nfoamCompiler=ThirdParty\nexport WM_MPLIB=OPENMPI\n```\n\n接着，打开`~/.bashrc` 文件，在最后一行输入\"source $HOME/OpenFOAM/OpenFOAM-2.3.x/etc/bashrc\"，保存后，在终端里运行一下\n```\nsource ~/.bashrc\n```\n来加载跟OpenFOAM相关的环境变量。\n\n最后，还需要检查一下 ThirdParty-2.3.x 目录下的那些编译辅助脚本，看看设置是否正确。需要检查的脚本有 `makeGcc`， `makeCGAL`， `makeCmake`，主要的检查项目仍然是看脚本里设置的软件包版本和实际下载的是否一致。比如，打开脚本 `makeGcc` ，有这么一段配置\n```\ngmpPACKAGE=gmp-5.1.2\nmpfrPACKAGE=mpfr-3.1.2\nmpcPACKAGE=mpc-1.0.1\ngccPACKAGE=gcc-4.8.2\n\n```\n需要保证这里的设置与 ThirdParty-2.3.x 目录下实际的源码包的版本一致。\n\n#### 1.4 编译过程\n上面的配置完成以后，就可以开始编译了。按如下顺序进行编译：\n+ gcc\n运行 ThirdParty-2.3.x 下的脚本 `makeGcc` 即可。\n这一步完成以后，在终端里输入 `gcc -v` ，看看返回的版本是否是下载的那个版本。如若不然，重新运行一下 `source ~/.bashrc` ，再看看 gcc 的版本是否正常。要是不对的话，那就得检查一下 gcc 的编译过程是否出错了。gcc 的编译成功是前提，要是这一步没有成功，下面的也就无法进行了。 \n+ CGAL\n运行 `makeCGAL` 即可，boost 和 CGAL 的编译包括在这一步。\n完成以后，也同样需要检查一下是否编译成功。检查的办法是，看看 `ThirdParty-2.3.x/platforms/linux64Gcc` 下是否有 `CGAL-4.3` 和 `boost-system`，然后看看这两个目录下是否都有 `lib` 和 `bin` 目录。如果没有，那就是编译出问题了，需要检查一下。 \n+ Cmake\n运行 makeCmake\n同样的，编译完以后需要检查是否成功。\n+ ThirdParty的其他包\n运行 ThirdParty-2.3.x 下面的 `Allwmake` ，这一步包括了openmpi 以及 Scotch 的编译。\n正常的话，这一步编译完以后，应该就可以有 `mpirun` 以及  `mpicc` 等命令了。请运行 `which mpirun` 来检查编译是否成功。  \n+ 编译OpenFOAM-2.3.x\n到 `$HOME/OpenFOAM/OpenFOAM-2.3.x` 下去运行 `Allwmake`，进行 OpenFOAM 的编译。  \n\n建议将编译过程的输出保留下来，万一编译失败，可以根据编译过程的报错来找原因。比如，上面说的 \"运行 makeGcc\" ，在终端里可以这样操作\n```\n./makeGcc > log_gcc 2>&1 &\n```\n这样，便会将编译 gcc 过程中的正常输出信息和报错信息都输出到文件 \"log_gcc\" 里面。此外，后来我发现，其实运行 ThirdParty-2.3.x 下面的 `Allwmake` 时，其实也包括了\"CGAL\"的编译，所以其实 `makeCGAL` 不需要单独拿出来作为一步。\n\n\n#### 1.5 测试\n编译结束以后，需要测试一下编译是否成功。建议至少运行一个串行算例，一个并行算例来检验 OpenFOAM 编译是否成功。最简单的是将 interFoam 求解器的 dambreak 算例串行运行一次，并行运行一次。\n\n#### 1.6 移植到集群\n如果上述编译一切正常，那就可以考虑将编译好的OpenFOAM移植到集群了。移植之前，为了减少需要拷贝的文件的数量，可以将一些不需要的源码和编译过程产生的中间文件删除。比如，ThirdParty-2.3.x 目录下的 gcc， mpfr，gmp，mpc以及 boost 的源码包都删除（openmpi-1.6.5 的源码建议保留），因为有用的是文件其实都在 platforms 目录下，只要保证这个目录完好就可以了。然后，建议将 `$HOME/OpenFOAM` 整个目录打包，再拷贝到集群上去。打包的目的有两个，一是减小文件的空间占用，另外一个更重要的原因是，OpenFOAM 的编译过程中会产生很多重要的软链接，如果不打包，这些软链接容易在拷贝的过程中损坏（比如，假如你用U盘拷贝 OpenFOAM 这个目录，那软链接几乎肯定会损坏）。 \n\n将压缩包拷贝到集群，在你的 $HOME 下解压。建议你在集群上也创建跟在虚拟上一样的目录结构，即在`$HOME`下创建目录 `OpenFOAM`，然后将 OpenFOAM-2.3.x 和 ThirdParty-2.3.x 拷贝到`OpenFOAM`目录下，这样，就不再需要去修改OpenFOAM的安装路径这个环境变量了。万一你没法做到这一点，那么，你需要修改 `OpenFOAM-2.3.x/etc/bashrc` 文件，将 `foamInstall=$HOME/$WM_PROJECT` 修改成你的实际路径（这里 `$HOME/$WM_PROJECT=$HOME/OpenFOAM`）。\n\n然后，类似的，打开`~/.bashrc` 文件，在最后一行输入\"source $HOME/OpenFOAM/OpenFOAM-2.3.x/etc/bashrc\"，保存后，在终端里运行一下\n```\nsource ~/.bashrc\n```\n一切正常的话，移植工作就完成了。\n\n当然，也需要测试一下移植是否成功，同样可以通过运行 dambreak算例来测试。我曾经遇到的问题是，串行可以运行，但是并行出问题了。对于这种情况，解决办法是，到 `OpenFOAM/OpenFOAM-2.3.x/src/Pstream` 目录下， `dummy` 和 `mpi` 下运行  `wclean`，然后，运行 `Pstream` 下面的  `Allwmake` ，重新编译 `dummy` 和 `mpi` 。如果这样编译了仍然不能并行，那么还可以再试试重新编译openmpi，具体做法是，删除 `ThirdParty-2.3.x/platforms/linux64GccDPOpt/lib/openmpi-1.6.5` 以及 `ThirdParty-2.3.x/platforms/linux64Gcc/openmpi-1.6.5`，然后重新运行ThirdParty-2.3.x 下的  `Allwmake`，编译完以后在重新编译一下 `dummy` 和 `mpi`。一般情况下，可以编译成功，因为编译需要的东西其实都在 ThirdParty-2.3.x 下面包含了，不需要依赖系统的什么包，这也是自己上面自己手动编译 gcc 等这些包带来的好处。这样处理以后，就可以正常并行运行了。\n\n### 2. CentOS 5.4\nCentOS 5.4 上的编译和移植过程几乎和上面是一样的，只有几个细节不同，比如，安装依赖包的时候，\n```\nyum install glibc-devel.i686\n```\n应该是\n```\nyum install glibc-devel.i386\n```\n其他的差别，在我印象中是没有了。\n\n最后，有必要提一下一个诡异的失败经历，这个问题我至今也不知道是什么原因导致的。\n有一次在移植到集群的时候，重新编以 openmpi 的过程中遇到如下报错：\n```\nCDPATH=\"${ZSH_VERSION+.}:\" && cd . && /bin/sh /storage02.mnt/home/lmu/OpenFOAM/ThirdParty-2.3.x/openmpi-1.6.5/config/missing --run aclocal-1.11 -I config\n```\n导致 openmpi 无法编译。\n但是，将OpenFOAM拷贝到另一个目录（不再是 `$HOME/OpenFOAM` ），再重新尝试，却成功了。\n\n另一次，是在 Scientific Linux 上，也是重新编译 openmpi 的时候遇到了一样的报错，后来解决的办法是，弃用ThirdParty下的 openmpi，改启用系统的openmpi（虽然版本老一点），具体设置是将\" export WM_MPLIB=OPENMPI\" 改为 \" export WM_MPLIB=SYSTEMOPENMPI\"，改完以后也能成功并行运行。\n\n\n本篇博文，由于是写在我编译 OpenFOAM 好几个月之后，当时也没有详细记录编译过程，所以肯定有细节遗漏或者错误的地方。而且，在不同的系统上，也可能遇到我这里没有提到的问题，本篇博文仅仅是给有需要的人做一个参考。要是你编译过程中遇到某个问题在看了本文后得到了解决，那我的目的就达到了。有问题 ，欢迎来OpenFOAM开源计算千人群讨论交流。\n\n\n** 2016.05.22 补充 **：\n最近编译了 OpenFOAM-v3.0+，使用 gcc-4.9.3 和 openMPI-1.10.0，在编译过程中，流程跟上面是一样的，但是有些额外的步骤。\n1. 编译 gcc 的时候，报错说找不到 lgcc_s，提示可能是缺少32位的库，经过 \" yum install libgcc \" 和 \" yum install libgcc.i686 \" 后，问题解决。\n2. 编译 openmpi-1.10.0 时，遇到提示 autoconf 和 aclocal 版本不对，经过源码编译安装 autoconf-2.69 和 automake-1.12 后，问题解决。\n","source":"_posts/OpenFOAM-install-centOS.md","raw":"title: \"在 CentOS 上安装 OpenFOAM \"\ndate: 2015-09-13 13:23:06\ncomments: true\ntags:\n - OpenFOAM\n - CentOS\ncategories:\n - OpenFOAM\n---\n\n本篇记录我在 CentOS 上编译安装 OpenFOAM 的过程。我需要在不能联网且没有 root 权限的集群上使用OpenFOAM ，最早的时候，我使用的是 [centFOAM project](http://sourceforge.net/projects/centfoam/) 提供的 64bit CentOS 安装包，这个很方便，把压缩包下载，解压，然后配置一下环境变量就可以了。但是后来 centFOAM 好像不再更新了，所以我只好尝试着自己编译。\n\n由于集群上缺少一些 OpenFOAM 依赖的包，要是一个一个去下载源码编译以补齐那些依赖的包，实在很费劲，所以我采取了另一种尝试：在虚拟机里安装跟集群上一样的系统，然后在虚拟机里编译好 OpenFOAM ，再拷贝到集群上去用（据信 cenoFOAM project 提供的安装包也是在虚拟机编译好的，而且在 cfd-online 论坛上也见有人推荐这么做）。我尝试过在 CentOS 5.4 ，CentOS 6.3 以及 Scientific Linux 6.5 上安装过 OpenFOAM-2.3.x，都成功了，过程大同小异。下面是我的安装过程的一个简要记录。\n\n<!--more-->\n\n### 1 CentOS 6.3\n首先，你需要一个虚拟机软件，我使用的是 VirtualBox，然后在虚拟机里安装一个64 bit 的CentOS 6.3（镜像可以去[官网](http://vault.centos.org/)下载）。注意，要想在 VirtualBox安装 64 bit的虚拟机，你的主系统也必须是 64 bit，而且，还要求你的 CPU 支持并开启了虚拟化技术，见[这个链接](http://rickie622.blog.163.com/blog/static/2123881120113473224536/)。然后，安装虚拟机的过程中，CentOS 6.3 会让你选择以哪种方式安装系统，我选择的是 \"Software Development Workstation\"，因为这种方式安装的包最全。以下是我在虚拟机里编译安装 OpenFOAM-2.3.x 的过程。\n\n\n#### 1.0 补充一些依赖包\n确保你的虚拟机可以联网，然后运行以下命令补充一些包：\n```\nyum groupinstall 'Development Tools'\n\nyum install glibc-devel.i686\n\nyum install zlib.x86_64\nyum install zlib-devel.x86_64\n```\n\n\n#### 1.1 下载需要的源码包。\n需要的源码包包括：OpenFOAM-2.3.x ，ThirdParty-2.3.x，这两个可以在OpenFOAM的官网找到，其中 OpenFOAM-2.3.x 可以用 git 从[这里](https://github.com/OpenFOAM/OpenFOAM-2.3.x)克隆一份；gcc-4.8.2, mpfr-3.1.2, gmp-5.1.2, mpc-1.0.1, boost-1.55.0, 这些可以OpenFOAM的 github 仓库里[找到链接](https://github.com/OpenFOAM/ThirdParty-2.3.x)。因为有移植到集群上的需要，所以我这里需要自己编译gcc 和openmpi，这样，就不需要依赖集群系统上的 gcc 和 openmpi 了。\n\n#### 1.2 将源码包解压到合适的位置\n建议在虚拟里，创建一个普通用户（假设为 user），不要用root用户来编译，以下操作都假定是以user用户的身份在进行。在 $HOME 下创建一个目录 OpenFOAM，然后将OpenFOAM-2.3.x 以及 ThirdParty-2.3.x 拷贝到该目录下（如果需要的话，先解压）。然后，解压 gcc-4.8.2，mpfr-3.1.2, gmp-5.1.2, mpc-1.0.1, boost-1.55.0, 并拷贝到 `$HOME/ThirdParty-2.3.x` 下，并将 boost-1.55.0 重命名为 boost-system。\n\n#### 1.3 编译前的配置和检查\n打开`$HOME/OpenFOAM/OpenFOAM-2.3.x/etc/config/settings.sh` 文件，跳到\n```\ncase \"${foamCompiler}\" in\nOpenFOAM | ThirdParty)\n    case \"$WM_COMPILER\" in\n    Gcc | Gcc++0x | Gcc48 | Gcc48++0x)\n        gcc_version=gcc-4.8.2\n        gmp_version=gmp-5.1.2\n        mpfr_version=mpfr-3.1.2\n        mpc_version=mpc-1.0.1\n        ;;\n```\n检查一下你下载的软件包版本跟这里的设置是否一样，如果不一致，可以直接修改这个文件以使这里的设置和你下载的版本一致。\n此外，还需要跳到\n```\nOPENMPI)\n    export FOAM_MPI=openmpi-1.6.5\n    # optional configuration tweaks:\n    _foamSource `$WM_PROJECT_DIR/bin/foamEtcFile config/openmpi.sh`\n\n    export MPI_ARCH_PATH=$WM_THIRD_PARTY_DIR/platforms/$WM_ARCH$WM_COMPILER/$FOAM_MPI\n\n    # Tell OpenMPI where to find its install directory\n    export OPAL_PREFIX=$MPI_ARCH_PATH\n\n    _foamAddPath    $MPI_ARCH_PATH/bin\n\n    # 64-bit on OpenSuSE 12.1 uses lib64 others use lib\n    _foamAddLib     $MPI_ARCH_PATH/lib$WM_COMPILER_LIB_ARCH\n    _foamAddLib     $MPI_ARCH_PATH/lib\n\n    _foamAddMan     $MPI_ARCH_PATH/share/man\n    ;;\n\n```\n看看你的 ThirdParty-2.3.x 下的 openmpi 的版本是否跟这里的一样（上面的设置是 openmpi-1.6.5），如果不一样，也需要修改这个文件。\n\n然后，打开`$HOME/OpenFOAM/OpenFOAM-2.3.x/etc/bashrc` 文件，设置\n```\nfoamCompiler=ThirdParty\nexport WM_MPLIB=OPENMPI\n```\n\n接着，打开`~/.bashrc` 文件，在最后一行输入\"source $HOME/OpenFOAM/OpenFOAM-2.3.x/etc/bashrc\"，保存后，在终端里运行一下\n```\nsource ~/.bashrc\n```\n来加载跟OpenFOAM相关的环境变量。\n\n最后，还需要检查一下 ThirdParty-2.3.x 目录下的那些编译辅助脚本，看看设置是否正确。需要检查的脚本有 `makeGcc`， `makeCGAL`， `makeCmake`，主要的检查项目仍然是看脚本里设置的软件包版本和实际下载的是否一致。比如，打开脚本 `makeGcc` ，有这么一段配置\n```\ngmpPACKAGE=gmp-5.1.2\nmpfrPACKAGE=mpfr-3.1.2\nmpcPACKAGE=mpc-1.0.1\ngccPACKAGE=gcc-4.8.2\n\n```\n需要保证这里的设置与 ThirdParty-2.3.x 目录下实际的源码包的版本一致。\n\n#### 1.4 编译过程\n上面的配置完成以后，就可以开始编译了。按如下顺序进行编译：\n+ gcc\n运行 ThirdParty-2.3.x 下的脚本 `makeGcc` 即可。\n这一步完成以后，在终端里输入 `gcc -v` ，看看返回的版本是否是下载的那个版本。如若不然，重新运行一下 `source ~/.bashrc` ，再看看 gcc 的版本是否正常。要是不对的话，那就得检查一下 gcc 的编译过程是否出错了。gcc 的编译成功是前提，要是这一步没有成功，下面的也就无法进行了。 \n+ CGAL\n运行 `makeCGAL` 即可，boost 和 CGAL 的编译包括在这一步。\n完成以后，也同样需要检查一下是否编译成功。检查的办法是，看看 `ThirdParty-2.3.x/platforms/linux64Gcc` 下是否有 `CGAL-4.3` 和 `boost-system`，然后看看这两个目录下是否都有 `lib` 和 `bin` 目录。如果没有，那就是编译出问题了，需要检查一下。 \n+ Cmake\n运行 makeCmake\n同样的，编译完以后需要检查是否成功。\n+ ThirdParty的其他包\n运行 ThirdParty-2.3.x 下面的 `Allwmake` ，这一步包括了openmpi 以及 Scotch 的编译。\n正常的话，这一步编译完以后，应该就可以有 `mpirun` 以及  `mpicc` 等命令了。请运行 `which mpirun` 来检查编译是否成功。  \n+ 编译OpenFOAM-2.3.x\n到 `$HOME/OpenFOAM/OpenFOAM-2.3.x` 下去运行 `Allwmake`，进行 OpenFOAM 的编译。  \n\n建议将编译过程的输出保留下来，万一编译失败，可以根据编译过程的报错来找原因。比如，上面说的 \"运行 makeGcc\" ，在终端里可以这样操作\n```\n./makeGcc > log_gcc 2>&1 &\n```\n这样，便会将编译 gcc 过程中的正常输出信息和报错信息都输出到文件 \"log_gcc\" 里面。此外，后来我发现，其实运行 ThirdParty-2.3.x 下面的 `Allwmake` 时，其实也包括了\"CGAL\"的编译，所以其实 `makeCGAL` 不需要单独拿出来作为一步。\n\n\n#### 1.5 测试\n编译结束以后，需要测试一下编译是否成功。建议至少运行一个串行算例，一个并行算例来检验 OpenFOAM 编译是否成功。最简单的是将 interFoam 求解器的 dambreak 算例串行运行一次，并行运行一次。\n\n#### 1.6 移植到集群\n如果上述编译一切正常，那就可以考虑将编译好的OpenFOAM移植到集群了。移植之前，为了减少需要拷贝的文件的数量，可以将一些不需要的源码和编译过程产生的中间文件删除。比如，ThirdParty-2.3.x 目录下的 gcc， mpfr，gmp，mpc以及 boost 的源码包都删除（openmpi-1.6.5 的源码建议保留），因为有用的是文件其实都在 platforms 目录下，只要保证这个目录完好就可以了。然后，建议将 `$HOME/OpenFOAM` 整个目录打包，再拷贝到集群上去。打包的目的有两个，一是减小文件的空间占用，另外一个更重要的原因是，OpenFOAM 的编译过程中会产生很多重要的软链接，如果不打包，这些软链接容易在拷贝的过程中损坏（比如，假如你用U盘拷贝 OpenFOAM 这个目录，那软链接几乎肯定会损坏）。 \n\n将压缩包拷贝到集群，在你的 $HOME 下解压。建议你在集群上也创建跟在虚拟上一样的目录结构，即在`$HOME`下创建目录 `OpenFOAM`，然后将 OpenFOAM-2.3.x 和 ThirdParty-2.3.x 拷贝到`OpenFOAM`目录下，这样，就不再需要去修改OpenFOAM的安装路径这个环境变量了。万一你没法做到这一点，那么，你需要修改 `OpenFOAM-2.3.x/etc/bashrc` 文件，将 `foamInstall=$HOME/$WM_PROJECT` 修改成你的实际路径（这里 `$HOME/$WM_PROJECT=$HOME/OpenFOAM`）。\n\n然后，类似的，打开`~/.bashrc` 文件，在最后一行输入\"source $HOME/OpenFOAM/OpenFOAM-2.3.x/etc/bashrc\"，保存后，在终端里运行一下\n```\nsource ~/.bashrc\n```\n一切正常的话，移植工作就完成了。\n\n当然，也需要测试一下移植是否成功，同样可以通过运行 dambreak算例来测试。我曾经遇到的问题是，串行可以运行，但是并行出问题了。对于这种情况，解决办法是，到 `OpenFOAM/OpenFOAM-2.3.x/src/Pstream` 目录下， `dummy` 和 `mpi` 下运行  `wclean`，然后，运行 `Pstream` 下面的  `Allwmake` ，重新编译 `dummy` 和 `mpi` 。如果这样编译了仍然不能并行，那么还可以再试试重新编译openmpi，具体做法是，删除 `ThirdParty-2.3.x/platforms/linux64GccDPOpt/lib/openmpi-1.6.5` 以及 `ThirdParty-2.3.x/platforms/linux64Gcc/openmpi-1.6.5`，然后重新运行ThirdParty-2.3.x 下的  `Allwmake`，编译完以后在重新编译一下 `dummy` 和 `mpi`。一般情况下，可以编译成功，因为编译需要的东西其实都在 ThirdParty-2.3.x 下面包含了，不需要依赖系统的什么包，这也是自己上面自己手动编译 gcc 等这些包带来的好处。这样处理以后，就可以正常并行运行了。\n\n### 2. CentOS 5.4\nCentOS 5.4 上的编译和移植过程几乎和上面是一样的，只有几个细节不同，比如，安装依赖包的时候，\n```\nyum install glibc-devel.i686\n```\n应该是\n```\nyum install glibc-devel.i386\n```\n其他的差别，在我印象中是没有了。\n\n最后，有必要提一下一个诡异的失败经历，这个问题我至今也不知道是什么原因导致的。\n有一次在移植到集群的时候，重新编以 openmpi 的过程中遇到如下报错：\n```\nCDPATH=\"${ZSH_VERSION+.}:\" && cd . && /bin/sh /storage02.mnt/home/lmu/OpenFOAM/ThirdParty-2.3.x/openmpi-1.6.5/config/missing --run aclocal-1.11 -I config\n```\n导致 openmpi 无法编译。\n但是，将OpenFOAM拷贝到另一个目录（不再是 `$HOME/OpenFOAM` ），再重新尝试，却成功了。\n\n另一次，是在 Scientific Linux 上，也是重新编译 openmpi 的时候遇到了一样的报错，后来解决的办法是，弃用ThirdParty下的 openmpi，改启用系统的openmpi（虽然版本老一点），具体设置是将\" export WM_MPLIB=OPENMPI\" 改为 \" export WM_MPLIB=SYSTEMOPENMPI\"，改完以后也能成功并行运行。\n\n\n本篇博文，由于是写在我编译 OpenFOAM 好几个月之后，当时也没有详细记录编译过程，所以肯定有细节遗漏或者错误的地方。而且，在不同的系统上，也可能遇到我这里没有提到的问题，本篇博文仅仅是给有需要的人做一个参考。要是你编译过程中遇到某个问题在看了本文后得到了解决，那我的目的就达到了。有问题 ，欢迎来OpenFOAM开源计算千人群讨论交流。\n\n\n** 2016.05.22 补充 **：\n最近编译了 OpenFOAM-v3.0+，使用 gcc-4.9.3 和 openMPI-1.10.0，在编译过程中，流程跟上面是一样的，但是有些额外的步骤。\n1. 编译 gcc 的时候，报错说找不到 lgcc_s，提示可能是缺少32位的库，经过 \" yum install libgcc \" 和 \" yum install libgcc.i686 \" 后，问题解决。\n2. 编译 openmpi-1.10.0 时，遇到提示 autoconf 和 aclocal 版本不对，经过源码编译安装 autoconf-2.69 和 automake-1.12 后，问题解决。\n","slug":"OpenFOAM-install-centOS","published":1,"updated":"2016-05-23T06:29:18.106Z","_id":"cioiqegfq004sz8mbkjl2wrim","layout":"post","photos":[],"link":""},{"title":"C++ 中派生类引用与基类引用的隐式转换","date":"2015-09-13T09:03:35.000Z","comments":1,"_content":"\n在读 OpenFOAM 代码过程中，有一类应用初看之下觉得很费解，比如 OpenFOAM-2.3.x 的 `twoPhaseEulerFoam`，createFields.H 有这么一段：\n```\nphaseModel& phase1 = fluid.phase1();\nphaseModel& phase2 = fluid.phase2();\n\nvolScalarField& alpha1 = phase1;\nvolScalarField& alpha2 = phase2;\n\n```\n乍看之下，感觉有点奇怪：怎么能将 phaseModel 类的引用直接赋值给 volScalarField 类的引用呢？后来查看了一下 phaseModel 类的定义，发现原来 phaseModel 类是 volScalarField 类的派生，由此上面代码就好理解了，无非是将派生类引用赋值给基类引用而已。\n\n<!--more-->\n\n但是，下面代码，虽然深究下去发现原理类似，但是乍看上去却更加费解：\n```\nFoam::tmp<Foam::volVectorField> Foam::twoPhaseSystem::U() const\n{\n    return phase1_*phase1_.U() + phase2_*phase2_.U();\n}\nFoam::tmp<Foam::surfaceScalarField> Foam::twoPhaseSystem::calcPhi() const\n{\n    return\n        fvc::interpolate(phase1_)*phase1_.phi()\n      + fvc::interpolate(phase2_)*phase2_.phi();\n}\n```\n这里将 `phase1_` 直接与 `phase1_.U()`（或者 `phase1_.phi()`）相乘，怎么理解？从原理上讲，应该是 `phase1_` 的体积分率与其速度的乘积。\n仔细看一下 phaseModel 类的构造函数，\n```\nFoam::phaseModel::phaseModel\n(\n    const twoPhaseSystem& fluid,\n    const dictionary& phaseProperties,\n    const word& phaseName\n)\n:\n    volScalarField\n    (\n        IOobject\n        (\n            IOobject::groupName(\"alpha\", phaseName), // \"alpha.particle\" \n            fluid.mesh().time().timeName(),\n            fluid.mesh(),\n            IOobject::READ_IF_PRESENT,\n            IOobject::AUTO_WRITE\n        ),\n        fluid.mesh(),\n        dimensionedScalar(\"alpha\", dimless, 0)\n    ),\n    ......\n    ......\n```\n原来是这样，读取 \"alpha.phaseName\" 数据文件构建了一个 IOobject 对象，并用该对象对一个临时的 volScalarField 对象进行了初始化，然后用成员初始化列表，将该临时 volScalarField 对象对基类 volScalarField 进行初始化。\n\n这样一来，对于上面的情形， `phase1_ * phase1_.U()` ，实际上是进行了一个隐式转换，先将 `phase1_` 转换成基类 volScalarField 类型，由于上述 phaseModel 类的初始化设定，转换以后，`phase1_` 其实就相当于用 alpha 初始化过的那个 volScalarField 类的对象了。所以， `phase1_ * phase1_.U()` 其实相当于  `alpha.phaseName * phase1_.U()` ，是两个 volScalarField 类对象之间的乘法运算。 \n\n在 `twoPhaseEulerFoam`的代码里，类似的用法还有很多，这里不能一一举例，当看到某处费解的时候，不妨想想是否是上面提到的情形。\n\n为了便于理解这个原理，我这里写了一个简单的 c++ 测试小程序\n```\n#include<iostream>\nusing namespace std;\n\nclass A\n{\n    public:\n        int a;\n        A(int a);\n        void print();\n\n};\nA::A(int a)\n{\n    this->a = a;\n}\nvoid A::print()\n{\n    cout<<\"base class:\"<<\"a=\"<<a<<endl;\n}\n\nclass B : public A\n{\n    public:\n        int b;\n        B(int a, int b);\n        void print();\n};\n\nB::B(int a, int b):A(a)\n{\n    this->b = b;\n}\nvoid B::print()\n{\n    cout<<\"Derived class:\"<<\"a=\"<<a<<\",b=\"<<b<<endl;\n}\n\nvoid print_test( A& obja)\n{\n    obja.print();\n}\n\nint operator*(A& a, A& b)\n{\n    return a.a * b.a;\n}\n\nint main(int argc, char *argv[])\n{\n    int a=2,b=4;\n\n    A obj1(a);\n    obj1.print();\n\n    B obj2(a+b,b);\n    obj2.print();\n\n    A& obj3 = obj2;\n    obj3.print();\n    cout<<\"obj3.a=\"<<obj3.a<<endl;\n   // cout<<\"obj3.b=\"<<obj3.b<<endl; Error! class A has no member named 'b'.\n\n    print_test(obj1);\n    print_test(obj2);\n\n    cout<<\"obj1 * obj2 :\"<< obj1 * obj2 << endl;\n\n    return 0;\n}\n```\n\n输出如下：\n\n```\nbase class:a=2\nDerived class:a=6,b=4\nbase class:a=6\nobj3.a=6\nbase class:a=2\nbase class:a=6\nobj1 * obj2 :12\n``` \n可以将上述程序中派生类对象赋值给基类的规律简单小结如下：\n+ 当将基类的引用指向派生类的对象时，用该引用只能调用派生类从基类继承而来的成员。像上面程序中，obj3.a 输出的是 对象 obj2 的成员 a，但是 obj3 无法调用成员b。\n+ 当一个函数需要的参数是基类的引用时，可以直接将派生类的对象传递给该函数，像上面的 `print_test` 函数和重载的 `*` 运算符那样。此事相当于作了一个隐式的将派生类对象赋值给基类引用。  \n","source":"_posts/CPP-conversion-Of-derived-class-reference-to-base-class-type.md","raw":"title: \"C++ 中派生类引用与基类引用的隐式转换\"\ndate: 2015-09-13 17:03:35\ncomments: true\ntags:\n - C++\n - OpenFOAM\ncategories:\n - C++\n---\n\n在读 OpenFOAM 代码过程中，有一类应用初看之下觉得很费解，比如 OpenFOAM-2.3.x 的 `twoPhaseEulerFoam`，createFields.H 有这么一段：\n```\nphaseModel& phase1 = fluid.phase1();\nphaseModel& phase2 = fluid.phase2();\n\nvolScalarField& alpha1 = phase1;\nvolScalarField& alpha2 = phase2;\n\n```\n乍看之下，感觉有点奇怪：怎么能将 phaseModel 类的引用直接赋值给 volScalarField 类的引用呢？后来查看了一下 phaseModel 类的定义，发现原来 phaseModel 类是 volScalarField 类的派生，由此上面代码就好理解了，无非是将派生类引用赋值给基类引用而已。\n\n<!--more-->\n\n但是，下面代码，虽然深究下去发现原理类似，但是乍看上去却更加费解：\n```\nFoam::tmp<Foam::volVectorField> Foam::twoPhaseSystem::U() const\n{\n    return phase1_*phase1_.U() + phase2_*phase2_.U();\n}\nFoam::tmp<Foam::surfaceScalarField> Foam::twoPhaseSystem::calcPhi() const\n{\n    return\n        fvc::interpolate(phase1_)*phase1_.phi()\n      + fvc::interpolate(phase2_)*phase2_.phi();\n}\n```\n这里将 `phase1_` 直接与 `phase1_.U()`（或者 `phase1_.phi()`）相乘，怎么理解？从原理上讲，应该是 `phase1_` 的体积分率与其速度的乘积。\n仔细看一下 phaseModel 类的构造函数，\n```\nFoam::phaseModel::phaseModel\n(\n    const twoPhaseSystem& fluid,\n    const dictionary& phaseProperties,\n    const word& phaseName\n)\n:\n    volScalarField\n    (\n        IOobject\n        (\n            IOobject::groupName(\"alpha\", phaseName), // \"alpha.particle\" \n            fluid.mesh().time().timeName(),\n            fluid.mesh(),\n            IOobject::READ_IF_PRESENT,\n            IOobject::AUTO_WRITE\n        ),\n        fluid.mesh(),\n        dimensionedScalar(\"alpha\", dimless, 0)\n    ),\n    ......\n    ......\n```\n原来是这样，读取 \"alpha.phaseName\" 数据文件构建了一个 IOobject 对象，并用该对象对一个临时的 volScalarField 对象进行了初始化，然后用成员初始化列表，将该临时 volScalarField 对象对基类 volScalarField 进行初始化。\n\n这样一来，对于上面的情形， `phase1_ * phase1_.U()` ，实际上是进行了一个隐式转换，先将 `phase1_` 转换成基类 volScalarField 类型，由于上述 phaseModel 类的初始化设定，转换以后，`phase1_` 其实就相当于用 alpha 初始化过的那个 volScalarField 类的对象了。所以， `phase1_ * phase1_.U()` 其实相当于  `alpha.phaseName * phase1_.U()` ，是两个 volScalarField 类对象之间的乘法运算。 \n\n在 `twoPhaseEulerFoam`的代码里，类似的用法还有很多，这里不能一一举例，当看到某处费解的时候，不妨想想是否是上面提到的情形。\n\n为了便于理解这个原理，我这里写了一个简单的 c++ 测试小程序\n```\n#include<iostream>\nusing namespace std;\n\nclass A\n{\n    public:\n        int a;\n        A(int a);\n        void print();\n\n};\nA::A(int a)\n{\n    this->a = a;\n}\nvoid A::print()\n{\n    cout<<\"base class:\"<<\"a=\"<<a<<endl;\n}\n\nclass B : public A\n{\n    public:\n        int b;\n        B(int a, int b);\n        void print();\n};\n\nB::B(int a, int b):A(a)\n{\n    this->b = b;\n}\nvoid B::print()\n{\n    cout<<\"Derived class:\"<<\"a=\"<<a<<\",b=\"<<b<<endl;\n}\n\nvoid print_test( A& obja)\n{\n    obja.print();\n}\n\nint operator*(A& a, A& b)\n{\n    return a.a * b.a;\n}\n\nint main(int argc, char *argv[])\n{\n    int a=2,b=4;\n\n    A obj1(a);\n    obj1.print();\n\n    B obj2(a+b,b);\n    obj2.print();\n\n    A& obj3 = obj2;\n    obj3.print();\n    cout<<\"obj3.a=\"<<obj3.a<<endl;\n   // cout<<\"obj3.b=\"<<obj3.b<<endl; Error! class A has no member named 'b'.\n\n    print_test(obj1);\n    print_test(obj2);\n\n    cout<<\"obj1 * obj2 :\"<< obj1 * obj2 << endl;\n\n    return 0;\n}\n```\n\n输出如下：\n\n```\nbase class:a=2\nDerived class:a=6,b=4\nbase class:a=6\nobj3.a=6\nbase class:a=2\nbase class:a=6\nobj1 * obj2 :12\n``` \n可以将上述程序中派生类对象赋值给基类的规律简单小结如下：\n+ 当将基类的引用指向派生类的对象时，用该引用只能调用派生类从基类继承而来的成员。像上面程序中，obj3.a 输出的是 对象 obj2 的成员 a，但是 obj3 无法调用成员b。\n+ 当一个函数需要的参数是基类的引用时，可以直接将派生类的对象传递给该函数，像上面的 `print_test` 函数和重载的 `*` 运算符那样。此事相当于作了一个隐式的将派生类对象赋值给基类引用。  \n","slug":"CPP-conversion-Of-derived-class-reference-to-base-class-type","published":1,"updated":"2015-09-13T13:19:36.361Z","layout":"post","photos":[],"link":"","_id":"cioiqegfu004xz8mbakcrocw4"},{"title":"OpenFOAM 中的边界条件（四）","date":"2016-04-02T10:16:39.000Z","comments":1,"_content":"\n#### JohnsonJackson 边界条件\n\n本篇来看用于气固两相流模拟的 JohnsonJackson 边界条件。这组边界条件用于设定双流体模型中固相在壁面的速度和颗粒温度。根据 [N. Reuge 2008, CES](http://www.sciencedirect.com/science/article/pii/S0009250908003904)，壁面上的固相速度和颗粒温度可以表示为：\n![JohnsonJackson boundary conditions](/image/boundaryConditions/JJ.png)\n其中，$u_m$ 指的是 m 相在壁面切向上的滑移速度。\n下面来看 OpenFOAM 中对这两个边界条件的实现\n\n<!--more-->\n\n在看 JohnsonJackson 边界条件之前，先要看一下 `partialSlip` 边界。\n\n##### partialSlip\n`partialSlipFvPatchField` 继承自 `transformFvPatchField`\n```\ntemplate<class Type>\nclass partialSlipFvPatchField\n:\n    public transformFvPatchField<Type>\n{\n    // Private data\n\n        //- Fraction (0-1) of value used for boundary condition\n        scalarField valueFraction_;\n        ......\n}\n```\n其定义了一个标量形式的 `valueFraction_` 。\n\n+ evaluate 函数\n```\ntemplate<class Type>\nvoid Foam::partialSlipFvPatchField<Type>::evaluate\n(\n    const Pstream::commsTypes\n)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    tmp<vectorField> nHat = this->patch().nf();\n\n    Field<Type>::operator=\n    (\n        (1.0 - valueFraction_)\n       *transform(I - sqr(nHat), this->patchInternalField())\n    );\n\n    transformFvPatchField<Type>::evaluate();\n}\n```\n与 `basicSymmetry` 相比，只是多了一项 `1.0 - valueFraction_` 。当 `valueFraction_ = 0` 时，其与 `basicSymmetry` 是一样的。\n\n+ snGradTransformDiag\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::partialSlipFvPatchField<Type>::snGradTransformDiag() const\n{\n    const vectorField nHat(this->patch().nf());\n    vectorField diag(nHat.size());\n\n    diag.replace(vector::X, mag(nHat.component(vector::X)));\n    diag.replace(vector::Y, mag(nHat.component(vector::Y)));\n    diag.replace(vector::Z, mag(nHat.component(vector::Z)));\n\n    return\n        valueFraction_*pTraits<Type>::one\n      + (1.0 - valueFraction_)\n       *transformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag));\n}\n```\n当 `valueFraction_ = 0` 时，这里的返回值与 `basicSymmetry` 也是一样的。\n+ snGrad\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::partialSlipFvPatchField<Type>::snGrad() const\n{\n    tmp<vectorField> nHat = this->patch().nf();\n    const Field<Type> pif(this->patchInternalField());\n\n    return\n    (\n        (1.0 - valueFraction_)*transform(I - sqr(nHat), pif) - pif\n    )*this->patch().deltaCoeffs();\n}\n```\n[User guide](http://cfd.direct/openfoam/user-guide/boundaries/) 里说这个边界是 `slip` 和 `zeroGradient` 的混合。 `valueFraction_ = 0` 时， `partialSlip` 与 `slip` 等价，这一点上面说明了。不过， 另一个极端，`valueFraction_ = 1` 时，却似乎不是跟 `zeroGradient` 等价。至少， `evaluate` 函数在`valueFraction_ = 1` 时与 `zeroGradient` 中的是不一样的。\n\n这里提到了 `slip` 这个边界，顺便再说一下， `slip` 边界继承自 `basicSymmetry` ，而且没有增加任何新的定义，所以， `slip` 与  `basicSymmetry` 的效果是等价的。对于标量， `slip` 与 `zeroGradient` 一样；对于矢量，以速度为例， `slip` 定义的边界上的速度值等于边界所属网格的速度的平行边界的分量，即\n$$\nu\\_p = u\\_C - (\\mathbf{I}-\\overrightarrow{n} \\otimes \\overrightarrow{n})\\cdot u\\_C\n$$\n\n\n##### JohnsonJacksonParticleSlip\n这个边界条件继承自 `partialSlip`，在此基础上额外定义了镜面反弹系数 `specularityCoefficient_` 。代码的核心在 `updateCoeffs` 函数\n```\nvoid Foam::JohnsonJacksonParticleSlipFvPatchVectorField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    // lookup the fluid model and the phase\n    const twoPhaseSystem& fluid = db().lookupObject<twoPhaseSystem>\n    (\n        \"phaseProperties\"\n    );\n\n    const phaseModel& phased\n    (\n        fluid.phase1().name() == dimensionedInternalField().group()\n      ? fluid.phase1()\n      : fluid.phase2()\n    );\n\n    // lookup all the fields on this patch\n    const fvPatchScalarField& alpha\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            phased.volScalarField::name()\n        )\n    );\n\n    const fvPatchScalarField& gs0\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"gs0\", phased.name())\n        )\n    );\n\n    const scalarField nu\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"nut\", phased.name())\n        )\n    );\n\n    word ThetaName(IOobject::groupName(\"Theta\", phased.name()));\n\n    const fvPatchScalarField& Theta\n    (\n        db().foundObject<volScalarField>(ThetaName)\n      ? patch().lookupPatchField<volScalarField, scalar>(ThetaName)\n      : alpha\n    );\n\n    // lookup the packed volume fraction\n    dimensionedScalar alphaMax\n    (\n        \"alphaMax\",\n        dimless,\n        db()\n       .lookupObject<IOdictionary>\n        (\n            IOobject::groupName(\"turbulenceProperties\", phased.name())\n        )\n       .subDict(\"RAS\")\n       .subDict(\"kineticTheoryCoeffs\")\n       .lookup(\"alphaMax\")\n    );\n\n    // calculate the slip value fraction\n    scalarField c\n    (\n        constant::mathematical::pi\n       *alpha\n       *gs0\n       *specularityCoefficient_.value()\n       *sqrt(3.0*Theta)\n       /max(6.0*nu*alphaMax.value(), SMALL)\n    );\n\n    this->valueFraction() = c/(c + patch().deltaCoeffs());\n\n    partialSlipFvPatchVectorField::updateCoeffs();\n}\n```\n这个函数主要的功能是重定义了继承自 `partialSlip` 中的 `valueFraction_`。结合 `partialSlip` 中的定义，可以知道最终 `JohnsonJacksonParticleSlip` 定义的边界速度的值为\n$$\nu\\_m=(1-\\frac{c}{c+\\Delta}) \\cdot (\\mathbf{I}-\\overrightarrow{n}\\otimes\\overrightarrow{n})\\cdot u\\_c\n$$\n其中 $u\\_c$ 为邻近壁面网格的 m 相的速度。`c` 的定义为：\n$$\nc=\\frac{\\pi \\varepsilon\\_m g\\_0 \\phi\\sqrt{3\\Theta}}{6.0\\nu\\_m\\varepsilon\\_m^{max}}\n$$\n对照上述公式(34)，根据 $c$ 的定义，这里可以把公式(34)简写为\n$$\n\\frac{\\partial u\\_m}{\\partial x}=-cu\\_m\n$$\n写成差分形式，即\n$$\n(u\\_m-u\\_{ct})\\cdot \\Delta=-cu\\_m\n$$\n于是得到\n$$\nu\\_m = \\frac{\\Delta}{c+\\Delta}u\\_{ct}\n$$\n若 $u\\_{ct}$ 定义为邻近壁面网格的速度壁面切向分量，则公式(34)与代码是一致的。\n\n##### JohnsonJacksonParticleTheta\n这个边界条件继承自 `mixed` ，此外新增了两个数据成员： `specularityCoefficient_` 和 `restitutionCoefficient_` 。核心的函数也是 `updateCoeffs` 。\n```\nvoid Foam::JohnsonJacksonParticleThetaFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    // lookup the fluid model and the phase\n    const twoPhaseSystem& fluid = db().lookupObject<twoPhaseSystem>\n    (\n        \"phaseProperties\"\n    );\n\n    const phaseModel& phased\n    (\n        fluid.phase1().name() == dimensionedInternalField().group()\n      ? fluid.phase1()\n      : fluid.phase2()\n    );\n\n    // lookup all the fields on this patch\n    const fvPatchScalarField& alpha\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            phased.volScalarField::name()\n        )\n    );\n\n    const fvPatchVectorField& U\n    (\n        patch().lookupPatchField<volVectorField, vector>\n        (\n            IOobject::groupName(\"U\", phased.name())\n        )\n    );\n\n    const fvPatchScalarField& gs0\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"gs0\", phased.name())\n        )\n    );\n\n    const fvPatchScalarField& kappa\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"kappa\", phased.name())\n        )\n    );\n\n    const scalarField Theta(patchInternalField());\n\n    // lookup the packed volume fraction\n    dimensionedScalar alphaMax\n    (\n        \"alphaMax\",\n        dimless,\n        db()\n       .lookupObject<IOdictionary>\n        (\n            IOobject::groupName(\"turbulenceProperties\", phased.name())\n        )\n       .subDict(\"RAS\")\n       .subDict(\"kineticTheoryCoeffs\")\n       .lookup(\"alphaMax\")\n    );\n\n    // calculate the reference value and the value fraction\n    if (restitutionCoefficient_.value() != 1.0)\n    {\n        this->refValue() =\n            (2.0/3.0)\n           *specularityCoefficient_.value()\n           *magSqr(U)\n           /(scalar(1) - sqr(restitutionCoefficient_.value()));\n\n        this->refGrad() = 0.0;\n\n        scalarField c\n        (\n             constant::mathematical::pi\n            *alpha\n            *gs0\n            *(scalar(1) - sqr(restitutionCoefficient_.value()))\n            *sqrt(3.0*Theta)\n            /max(4.0*kappa*alphaMax.value(), SMALL)\n        );\n\n        this->valueFraction() = c/(c + patch().deltaCoeffs());\n    }\n\n    // for a restitution coefficient of 1, the boundary degenerates to a fixed\n    // gradient condition\n    else\n    {\n        this->refValue() = 0.0;\n\n        this->refGrad() =\n            pos(alpha - SMALL)\n           *constant::mathematical::pi\n           *specularityCoefficient_.value()\n           *alpha\n           *gs0\n           *sqrt(3.0*Theta)\n           *magSqr(U)\n           /max(6.0*kappa*alphaMax.value(), SMALL);\n\n        this->valueFraction() = 0.0;\n    }\n    mixedFvPatchScalarField::updateCoeffs();\n}\n```\n这里分两种情况，即 `restitutionCoefficient_` 是否等于1。其实从公式(35)也能看出来，$e\\_w=1$ 与 $e\\_w \\neq 1$ 是不一样的。\n + $e\\_w\\neq 1$\n 这时，重定义了 `refValue` 和 `valueFraction`。利用辅助变量 $c$ 的定义，可以将公式(35)简化如下：\n$$\n\\frac{\\partial \\Theta\\_m}{\\partial x}=c \\cdot refValue - c \\cdot \\Theta\\_m\n$$\n写成差分形式\n$$\n(\\Theta\\_m-\\Theta\\_c) \\cdot \\Delta = c\\cdot refValue - c \\cdot \\Theta\\_m\n$$\n得\n$$\n\\Theta\\_m=\\frac{c}{c+\\Delta}\\cdot refValue + \\frac{\\Delta}{c+\\Delta} \\cdot \\Theta\\_c\n$$\n其中 $\\Theta\\_c$ 为邻近壁面网格的颗粒温度。\n根据 `mixed` 的定义，壁面的值应当是 `valureFraction * refValue + (1-valueFraction)*(patchInternalField() + refGrad/delta)`。 这里将 `refGrad` 赋值为0，就与公式一致了。\n\n + $e\\_w = 1$\n 这种情况下， `JohnsonJacksonParticleTheta` 就退化为简单的 `fixedGradient` 了。若 $ \\varepsilon\\_m $ 特别小，则为零梯度，否则，固定梯度，梯度值等于公式(35)的右边第一项。\n\n\n","source":"_posts/Boundary-conditions-in-OpenFOAM4.md","raw":"title: \"OpenFOAM 中的边界条件（四）\"\ndate: 2016-04-02 18:16:39\ncomments: true\ntags:\n- Boundary conditions\ncategories:\n- OpenFOAM\n---\n\n#### JohnsonJackson 边界条件\n\n本篇来看用于气固两相流模拟的 JohnsonJackson 边界条件。这组边界条件用于设定双流体模型中固相在壁面的速度和颗粒温度。根据 [N. Reuge 2008, CES](http://www.sciencedirect.com/science/article/pii/S0009250908003904)，壁面上的固相速度和颗粒温度可以表示为：\n![JohnsonJackson boundary conditions](/image/boundaryConditions/JJ.png)\n其中，$u_m$ 指的是 m 相在壁面切向上的滑移速度。\n下面来看 OpenFOAM 中对这两个边界条件的实现\n\n<!--more-->\n\n在看 JohnsonJackson 边界条件之前，先要看一下 `partialSlip` 边界。\n\n##### partialSlip\n`partialSlipFvPatchField` 继承自 `transformFvPatchField`\n```\ntemplate<class Type>\nclass partialSlipFvPatchField\n:\n    public transformFvPatchField<Type>\n{\n    // Private data\n\n        //- Fraction (0-1) of value used for boundary condition\n        scalarField valueFraction_;\n        ......\n}\n```\n其定义了一个标量形式的 `valueFraction_` 。\n\n+ evaluate 函数\n```\ntemplate<class Type>\nvoid Foam::partialSlipFvPatchField<Type>::evaluate\n(\n    const Pstream::commsTypes\n)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    tmp<vectorField> nHat = this->patch().nf();\n\n    Field<Type>::operator=\n    (\n        (1.0 - valueFraction_)\n       *transform(I - sqr(nHat), this->patchInternalField())\n    );\n\n    transformFvPatchField<Type>::evaluate();\n}\n```\n与 `basicSymmetry` 相比，只是多了一项 `1.0 - valueFraction_` 。当 `valueFraction_ = 0` 时，其与 `basicSymmetry` 是一样的。\n\n+ snGradTransformDiag\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::partialSlipFvPatchField<Type>::snGradTransformDiag() const\n{\n    const vectorField nHat(this->patch().nf());\n    vectorField diag(nHat.size());\n\n    diag.replace(vector::X, mag(nHat.component(vector::X)));\n    diag.replace(vector::Y, mag(nHat.component(vector::Y)));\n    diag.replace(vector::Z, mag(nHat.component(vector::Z)));\n\n    return\n        valueFraction_*pTraits<Type>::one\n      + (1.0 - valueFraction_)\n       *transformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag));\n}\n```\n当 `valueFraction_ = 0` 时，这里的返回值与 `basicSymmetry` 也是一样的。\n+ snGrad\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::partialSlipFvPatchField<Type>::snGrad() const\n{\n    tmp<vectorField> nHat = this->patch().nf();\n    const Field<Type> pif(this->patchInternalField());\n\n    return\n    (\n        (1.0 - valueFraction_)*transform(I - sqr(nHat), pif) - pif\n    )*this->patch().deltaCoeffs();\n}\n```\n[User guide](http://cfd.direct/openfoam/user-guide/boundaries/) 里说这个边界是 `slip` 和 `zeroGradient` 的混合。 `valueFraction_ = 0` 时， `partialSlip` 与 `slip` 等价，这一点上面说明了。不过， 另一个极端，`valueFraction_ = 1` 时，却似乎不是跟 `zeroGradient` 等价。至少， `evaluate` 函数在`valueFraction_ = 1` 时与 `zeroGradient` 中的是不一样的。\n\n这里提到了 `slip` 这个边界，顺便再说一下， `slip` 边界继承自 `basicSymmetry` ，而且没有增加任何新的定义，所以， `slip` 与  `basicSymmetry` 的效果是等价的。对于标量， `slip` 与 `zeroGradient` 一样；对于矢量，以速度为例， `slip` 定义的边界上的速度值等于边界所属网格的速度的平行边界的分量，即\n$$\nu\\_p = u\\_C - (\\mathbf{I}-\\overrightarrow{n} \\otimes \\overrightarrow{n})\\cdot u\\_C\n$$\n\n\n##### JohnsonJacksonParticleSlip\n这个边界条件继承自 `partialSlip`，在此基础上额外定义了镜面反弹系数 `specularityCoefficient_` 。代码的核心在 `updateCoeffs` 函数\n```\nvoid Foam::JohnsonJacksonParticleSlipFvPatchVectorField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    // lookup the fluid model and the phase\n    const twoPhaseSystem& fluid = db().lookupObject<twoPhaseSystem>\n    (\n        \"phaseProperties\"\n    );\n\n    const phaseModel& phased\n    (\n        fluid.phase1().name() == dimensionedInternalField().group()\n      ? fluid.phase1()\n      : fluid.phase2()\n    );\n\n    // lookup all the fields on this patch\n    const fvPatchScalarField& alpha\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            phased.volScalarField::name()\n        )\n    );\n\n    const fvPatchScalarField& gs0\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"gs0\", phased.name())\n        )\n    );\n\n    const scalarField nu\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"nut\", phased.name())\n        )\n    );\n\n    word ThetaName(IOobject::groupName(\"Theta\", phased.name()));\n\n    const fvPatchScalarField& Theta\n    (\n        db().foundObject<volScalarField>(ThetaName)\n      ? patch().lookupPatchField<volScalarField, scalar>(ThetaName)\n      : alpha\n    );\n\n    // lookup the packed volume fraction\n    dimensionedScalar alphaMax\n    (\n        \"alphaMax\",\n        dimless,\n        db()\n       .lookupObject<IOdictionary>\n        (\n            IOobject::groupName(\"turbulenceProperties\", phased.name())\n        )\n       .subDict(\"RAS\")\n       .subDict(\"kineticTheoryCoeffs\")\n       .lookup(\"alphaMax\")\n    );\n\n    // calculate the slip value fraction\n    scalarField c\n    (\n        constant::mathematical::pi\n       *alpha\n       *gs0\n       *specularityCoefficient_.value()\n       *sqrt(3.0*Theta)\n       /max(6.0*nu*alphaMax.value(), SMALL)\n    );\n\n    this->valueFraction() = c/(c + patch().deltaCoeffs());\n\n    partialSlipFvPatchVectorField::updateCoeffs();\n}\n```\n这个函数主要的功能是重定义了继承自 `partialSlip` 中的 `valueFraction_`。结合 `partialSlip` 中的定义，可以知道最终 `JohnsonJacksonParticleSlip` 定义的边界速度的值为\n$$\nu\\_m=(1-\\frac{c}{c+\\Delta}) \\cdot (\\mathbf{I}-\\overrightarrow{n}\\otimes\\overrightarrow{n})\\cdot u\\_c\n$$\n其中 $u\\_c$ 为邻近壁面网格的 m 相的速度。`c` 的定义为：\n$$\nc=\\frac{\\pi \\varepsilon\\_m g\\_0 \\phi\\sqrt{3\\Theta}}{6.0\\nu\\_m\\varepsilon\\_m^{max}}\n$$\n对照上述公式(34)，根据 $c$ 的定义，这里可以把公式(34)简写为\n$$\n\\frac{\\partial u\\_m}{\\partial x}=-cu\\_m\n$$\n写成差分形式，即\n$$\n(u\\_m-u\\_{ct})\\cdot \\Delta=-cu\\_m\n$$\n于是得到\n$$\nu\\_m = \\frac{\\Delta}{c+\\Delta}u\\_{ct}\n$$\n若 $u\\_{ct}$ 定义为邻近壁面网格的速度壁面切向分量，则公式(34)与代码是一致的。\n\n##### JohnsonJacksonParticleTheta\n这个边界条件继承自 `mixed` ，此外新增了两个数据成员： `specularityCoefficient_` 和 `restitutionCoefficient_` 。核心的函数也是 `updateCoeffs` 。\n```\nvoid Foam::JohnsonJacksonParticleThetaFvPatchScalarField::updateCoeffs()\n{\n    if (updated())\n    {\n        return;\n    }\n\n    // lookup the fluid model and the phase\n    const twoPhaseSystem& fluid = db().lookupObject<twoPhaseSystem>\n    (\n        \"phaseProperties\"\n    );\n\n    const phaseModel& phased\n    (\n        fluid.phase1().name() == dimensionedInternalField().group()\n      ? fluid.phase1()\n      : fluid.phase2()\n    );\n\n    // lookup all the fields on this patch\n    const fvPatchScalarField& alpha\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            phased.volScalarField::name()\n        )\n    );\n\n    const fvPatchVectorField& U\n    (\n        patch().lookupPatchField<volVectorField, vector>\n        (\n            IOobject::groupName(\"U\", phased.name())\n        )\n    );\n\n    const fvPatchScalarField& gs0\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"gs0\", phased.name())\n        )\n    );\n\n    const fvPatchScalarField& kappa\n    (\n        patch().lookupPatchField<volScalarField, scalar>\n        (\n            IOobject::groupName(\"kappa\", phased.name())\n        )\n    );\n\n    const scalarField Theta(patchInternalField());\n\n    // lookup the packed volume fraction\n    dimensionedScalar alphaMax\n    (\n        \"alphaMax\",\n        dimless,\n        db()\n       .lookupObject<IOdictionary>\n        (\n            IOobject::groupName(\"turbulenceProperties\", phased.name())\n        )\n       .subDict(\"RAS\")\n       .subDict(\"kineticTheoryCoeffs\")\n       .lookup(\"alphaMax\")\n    );\n\n    // calculate the reference value and the value fraction\n    if (restitutionCoefficient_.value() != 1.0)\n    {\n        this->refValue() =\n            (2.0/3.0)\n           *specularityCoefficient_.value()\n           *magSqr(U)\n           /(scalar(1) - sqr(restitutionCoefficient_.value()));\n\n        this->refGrad() = 0.0;\n\n        scalarField c\n        (\n             constant::mathematical::pi\n            *alpha\n            *gs0\n            *(scalar(1) - sqr(restitutionCoefficient_.value()))\n            *sqrt(3.0*Theta)\n            /max(4.0*kappa*alphaMax.value(), SMALL)\n        );\n\n        this->valueFraction() = c/(c + patch().deltaCoeffs());\n    }\n\n    // for a restitution coefficient of 1, the boundary degenerates to a fixed\n    // gradient condition\n    else\n    {\n        this->refValue() = 0.0;\n\n        this->refGrad() =\n            pos(alpha - SMALL)\n           *constant::mathematical::pi\n           *specularityCoefficient_.value()\n           *alpha\n           *gs0\n           *sqrt(3.0*Theta)\n           *magSqr(U)\n           /max(6.0*kappa*alphaMax.value(), SMALL);\n\n        this->valueFraction() = 0.0;\n    }\n    mixedFvPatchScalarField::updateCoeffs();\n}\n```\n这里分两种情况，即 `restitutionCoefficient_` 是否等于1。其实从公式(35)也能看出来，$e\\_w=1$ 与 $e\\_w \\neq 1$ 是不一样的。\n + $e\\_w\\neq 1$\n 这时，重定义了 `refValue` 和 `valueFraction`。利用辅助变量 $c$ 的定义，可以将公式(35)简化如下：\n$$\n\\frac{\\partial \\Theta\\_m}{\\partial x}=c \\cdot refValue - c \\cdot \\Theta\\_m\n$$\n写成差分形式\n$$\n(\\Theta\\_m-\\Theta\\_c) \\cdot \\Delta = c\\cdot refValue - c \\cdot \\Theta\\_m\n$$\n得\n$$\n\\Theta\\_m=\\frac{c}{c+\\Delta}\\cdot refValue + \\frac{\\Delta}{c+\\Delta} \\cdot \\Theta\\_c\n$$\n其中 $\\Theta\\_c$ 为邻近壁面网格的颗粒温度。\n根据 `mixed` 的定义，壁面的值应当是 `valureFraction * refValue + (1-valueFraction)*(patchInternalField() + refGrad/delta)`。 这里将 `refGrad` 赋值为0，就与公式一致了。\n\n + $e\\_w = 1$\n 这种情况下， `JohnsonJacksonParticleTheta` 就退化为简单的 `fixedGradient` 了。若 $ \\varepsilon\\_m $ 特别小，则为零梯度，否则，固定梯度，梯度值等于公式(35)的右边第一项。\n\n\n","slug":"Boundary-conditions-in-OpenFOAM4","published":1,"updated":"2016-04-03T03:42:14.824Z","layout":"post","photos":[],"link":"","_id":"cioiqegfx0051z8mbgbon1ay4"},{"title":"OpenFOAM 中的边界条件（三）","date":"2016-04-02T09:29:16.000Z","comments":1,"_content":"\nOpenFOAM 中有很多复杂的边界都是继承自上篇中提到的三个基础边界条件，这些边界条件的代码在上一篇的基础上就很容易看懂了。只不过，还有一些边界条件，不是继承自这三个基础边界条件的，其中有一些都直接或间接继承自另一个重要的边界条件： `transformFvPatchField`。本篇来看看这个 `transformFvPatchField` 以及几个继承自它的边界条件。\n\n<!--more-->\n\n##### 5. transform\n这是一个抽象基类，主要注意一下四个函数的定义：\n```\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return pTraits<Type>::one - snGradTransformDiag();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return\n        *this\n      - cmptMultiply\n        (\n            valueInternalCoeffs(this->patch().weights()),\n            this->patchInternalField()\n        );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return -this->patch().deltaCoeffs()*snGradTransformDiag();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return\n        snGrad()\n      - cmptMultiply(gradientInternalCoeffs(), this->patchInternalField());\n}\n```\n由于 `snGrad` 和 `snGradTransformDiag` 都是纯虚函数，所以这四个函数的具体返回值需要在派生类中实现了 `snGrad` 和 `snGradTransformDiag` 之后才能确定。\n另外注意，当模板参数为 `scalar` 时， `gradientInternalCoeffs` 函数有特殊的定义：\n```\ntemplate<>\ntmp<scalarField > transformFvPatchField<scalar>::gradientInternalCoeffs() const\n{\n    return tmp<scalarField >(new scalarField(size(), 0.0));\n}\n```\n\n#####6. directionMixed\n这个类，跟前面的 `mixed` 有点类似，但是又继承自 `transform` ，所以，似乎是二者的结合。\n```\ntemplate<class Type>\nclass directionMixedFvPatchField\n:\n    public transformFvPatchField<Type>\n{\n    // Private data\n\n        //- Value field\n        Field<Type> refValue_;\n\n        //- Normal gradient field\n        Field<Type> refGrad_;\n\n        //- Fraction (0-1) of value used for boundary condition\n        symmTensorField valueFraction_;\n```\n与 `mixed` 相似之处是，这里也定义了 `refValue_` ， `refGrad_` 和 `valueFraction_` 三个参数，所不同的是，这里的 `valueFraction_` 是一个对称张量。\n\n接下来， `directionMixed` 定义了 `snGrad` 和 `snGradTransformDiag` 这两个函数\n+ `snGrad` 和 `snGradTransformDiag`\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::directionMixedFvPatchField<Type>::snGrad() const\n{\n    const Field<Type> pif(this->patchInternalField());\n\n    tmp<Field<Type> > normalValue = transform(valueFraction_, refValue_);\n\n    tmp<Field<Type> > gradValue = pif + refGrad_/this->patch().deltaCoeffs();\n\n    tmp<Field<Type> > transformGradValue =\n        transform(I - valueFraction_, gradValue);\n\n    return\n        (normalValue + transformGradValue - pif)*\n        this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::directionMixedFvPatchField<Type>::snGradTransformDiag() const\n{\n    vectorField diag(valueFraction_.size());\n\n    diag.replace\n    (\n        vector::X,\n        sqrt(mag(valueFraction_.component(symmTensor::XX)))\n    );\n    diag.replace\n    (\n        vector::Y,\n        sqrt(mag(valueFraction_.component(symmTensor::YY)))\n    );\n    diag.replace\n    (\n        vector::Z,\n        sqrt(mag(valueFraction_.component(symmTensor::ZZ)))\n    );\n\n    return transformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag));\n}\n```\n\n+ `evaluate` 函数\n```\ntemplate<class Type>\nvoid Foam::directionMixedFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    tmp<Field<Type> > normalValue = transform(valueFraction_, refValue_);\n\n    tmp<Field<Type> > gradValue =\n        this->patchInternalField() + refGrad_/this->patch().deltaCoeffs();\n\n    tmp<Field<Type> > transformGradValue =\n        transform(I - valueFraction_, gradValue);\n\n    Field<Type>::operator=(normalValue + transformGradValue);\n\n    transformFvPatchField<Type>::evaluate();\n}\n```\n\n##### 7. basicSymmetry\n这个类的结构与 `directionMixed` 类似，对 `snGrad` ， `snGradTransformDiag` 和 `evaluate` 等几个函数进行了重新定义。\n+  `snGrad` 和 `snGradTransformDiag`\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::basicSymmetryFvPatchField<Type>::snGrad() const\n{\n    tmp<vectorField> nHat = this->patch().nf();\n\n    const Field<Type> iF(this->patchInternalField());\n\n    return\n        (transform(I - 2.0*sqr(nHat), iF) - iF)\n       *(this->patch().deltaCoeffs()/2.0);\n}\n\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::basicSymmetryFvPatchField<Type>::snGradTransformDiag() const\n{\n    const vectorField nHat(this->patch().nf());\n\n    vectorField diag(nHat.size());\n\n    diag.replace(vector::X, mag(nHat.component(vector::X)));\n    diag.replace(vector::Y, mag(nHat.component(vector::Y)));\n    diag.replace(vector::Z, mag(nHat.component(vector::Z)));\n\n    return transformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag));\n}\n```\n\n+ evaluate\n```\ntemplate<class Type>\nvoid Foam::basicSymmetryFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    tmp<vectorField> nHat = this->patch().nf();\n\n    const Field<Type> iF(this->patchInternalField());\n\n    Field<Type>::operator=\n    (\n        (iF + transform(I - 2.0*sqr(nHat), iF))/2.0\n    );\n\n    transformFvPatchField<Type>::evaluate();\n}\n```\n另外，值得注意的是，当模板参数 `Type` 是 `scalar` 时， `snGrad` 和 `evaluate` 函数有其他的定义：\n```\ntemplate<>\nFoam::tmp<Foam::scalarField>\nFoam::basicSymmetryFvPatchField<Foam::scalar>::snGrad() const\n{\n    return tmp<scalarField >(new scalarField(size(), 0.0));\n}\n\ntemplate<>\nvoid Foam::basicSymmetryFvPatchField<Foam::scalar>::evaluate\n(\n    const Pstream::commsTypes\n)\n{\n    if (!updated())\n    {\n        updateCoeffs();\n    }\n\n    scalarField::operator=(patchInternalField());\n    transformFvPatchField<scalar>::evaluate();\n}\n```\n从这两个函数可推断，当 `Type = scalar` 时， `basicSymmetry` 其实就相当于 `zeroGradient`。 \n\n\n\n关于 `transform` 和 `transformFieldMask` 这两个函数，摸索了很久。前者涉及的源文件有 [`symmTransformField.C`](http://foam.sourceforge.net/docs/cpp/a07932_source.html) ，[ `transformFieldTemplates.C` ](http://foam.sourceforge.net/docs/cpp/a07942_source.html)；后者的定义在 [`symmTransformField.H`](http://foam.sourceforge.net/docs/cpp/a07933_source.html)，涉及到的 `pow` 函数的定义在[`FieldFunctions.C`](http://foam.sourceforge.net/docs/cpp/a07889_source.html)。此外，这两个函数还需要用到类似 `TFOR_ALL_F_OP_FUNC_F_F` 的宏，定义在[`fieldM.H`](http://foam.sourceforge.net/docs/cpp/a07893_source.html#l00153)，而这个宏里涉及到的类似 `List_ELEM` 这样的宏，则定义在 [`ListLoopM.H`](http://foam.sourceforge.net/docs/cpp/a07587_source.html)。\n\n看了这么多，仍然无法完全确定这两个函数的具体的行为。主要的障碍在于那个 `pow` 函数实在看不明白。最后只好来对这两个函数进行了一些测试，测试结果总结如下：\n```\ntransform(tensorField p1, vectorField p2)\n```\n返回的是另一个 `vectorField` ，其值等于 `p1` 与 `p2` 的内积（即点乘）。注意，一般使用过程中总能保证 `p1.size() == p2.size()`，但是如果 `p1.size() > p2.size()`， 则返回结果的 `size` 等于 `p2.size()` ，值则等于 `p1` 的前 `p2.size()` 部分与 `p2` 的内积。\n[Boundary Conditions in OpenFOAM](http://www.slideshare.net/fumiyanozaki96/boundary-conditions-in-openfoam) 这个 silde 也提到了29页也同样提到了 `transform` 函数的作用\n```\ninline scalar transform(constsymmTensor&, const scalar s)\n{\n    return s;\n}\n\ntemplate<class Cmpt>\ninline Vector<Cmpt> transform(const symmTensor& stt, const Vector<Cmpt>& v)\n{\n    return stt & v;\n}\n```\n\n而 `transformFieldMask`\n```\ntransformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag))\n```\n目前测试的结果是，其返回值等于 `diag` 。\n\n\n有了上面对 `transform` 和 `transformFieldMask` 两个函数的测试结果，就可以来分析 `basicSymmetry` 和 `directionMixed` 两个边界条件的行为了。\n##### basicSymmetry\n对于标量，前面说过其等价于 `zeroGradient`，所以这里只分析矢量的情形。 \n从 `evaluate` 函数，可以得到如下公式\n$$\n\\begin{align}\n\\overrightarrow{\\phi}\\_b = & \\left [\\overrightarrow{\\phi}\\_c + (\\mathrm{I} - 2\\overrightarrow{n} \\otimes \\overrightarrow{n})\\cdot \\overrightarrow{\\phi}\\_c  \\right ] \\cdot \\frac{1}{2.0} \\\\\\\\\n= &  \\overrightarrow{\\phi}\\_c- \\left ( \\overrightarrow{\\phi}\\_c \\cdot  \\overrightarrow{n} \\right)\\cdot \\overrightarrow{n}\n\\end{align}\n$$\n这意味着，边界上的值等于其邻近网格中心的值的切向分量。\n为了方便分析四个系数，将上式写成分量的形式:\n$$\n\\begin{align}\n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} = & \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix} -\n\\begin{bmatrix}\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_x \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_y \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_z\n\\end{bmatrix} \\\\\\\\\n= & \\begin{bmatrix}\n(1-n\\_xn\\_x)\\phi\\_{cx} \\\\\\\\\n(1-n\\_yn\\_y)\\phi\\_{cy} \\\\\\\\\n(1-n\\_zn\\_z)\\phi\\_{cz}\n\\end{bmatrix} - \n\\begin{bmatrix}\n\\phi\\_{cy}n\\_yn\\_x + \\phi\\_{cz}n\\_zn\\_x \\\\\\\\\n\\phi\\_{cx}n\\_xn\\_y + \\phi\\_{cz}n\\_zn\\_y \\\\\\\\\n\\phi\\_{cx}n\\_xn\\_z + \\phi\\_{cy}n\\_yn\\_z \n\\end{bmatrix} \n\\end{align}\n$$\n照此公式，可以分析得到四个系数如下：\n$$\nvalueInternalCoeffs = \n\\begin{bmatrix}\n(1-n\\_xn\\_x) \\\\\\\\\n(1-n\\_yn\\_y) \\\\\\\\\n(1-n\\_zn\\_z)\n\\end{bmatrix}\n$$\n$$\nvalueBoundaryCoeffs = \n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} - valueInternalCoeffs \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n$$\ngradientInternalCoeffs= - \\Delta\n\\begin{bmatrix}\n(n\\_xn\\_x) \\\\\\\\\n(n\\_yn\\_y) \\\\\\\\\n(n\\_zn\\_z)\n\\end{bmatrix}\n$$\n$$\ngradientBoundaryCoeffs = - \\begin{bmatrix}\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_x \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_y \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_z\n\\end{bmatrix}  \\Delta - gradientInternalCoeffs  \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n\n** 但是，实际上 OpenFOAM 里不是这么实现的**！关键就在于这个 `snGradTransformDiag` 函数的定义与预期不符。\n根据我的测试， `snGradTransformDiag` 函数返回值应该是\n$$\nsnGradTransformDiag = \n\\begin{bmatrix}\n|n\\_x| \\\\\\\\\n|n\\_y| \\\\\\\\\n|n\\_z|\n\\end{bmatrix}\n$$\n即，张量 $\\overrightarrow{n}\\otimes\\overrightarrow{n}$ 的主对角线元素组成的矢量。\n\n而 `snGrad` 函数的返回值，根据代码可知\n$$\nsnGrad = - \\left ( \\overrightarrow{\\phi}\\_c \\cdot  \\overrightarrow{n} \\right)\\cdot \\overrightarrow{n}\\cdot \\Delta\n$$\n所以，OpenFOAM 中定义的四个系数为：\n$$\nvalueInternalCoeffs = \n\\begin{bmatrix}\n(1-|n\\_x|) \\\\\\\\\n(1-|n\\_y|) \\\\\\\\\n(1-|n\\_z|)\n\\end{bmatrix}\n$$\n$$\nvalueBoundaryCoeffs = \n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} - valueInternalCoeffs \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n$$\ngradientInternalCoeffs= - \\Delta\n\\begin{bmatrix}\n|n\\_x| \\\\\\\\\n|n\\_y| \\\\\\\\\n|n\\_z|\n\\end{bmatrix}\n$$\n$$\ngradientBoundaryCoeffs = - \\begin{bmatrix}\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_x \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_y \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_z\n\\end{bmatrix}  \\Delta - gradientInternalCoeffs  \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n\n这里的 $\\Delta$ 代表代码中的 `deltaCoeffs` 。\n\n##### directionMixed \n`directionMixed` 与 `basicSymmetry` 是类似的，差别在于 `directionMixed` 所使用的对称张量是指定的，而不一定是 $\\overrightarrow{n}\\otimes\\overrightarrow{n}$。\n根据 `evaluate` 函数，可以得到如下公式：\n$$\n\\begin{align}\n\\overrightarrow{\\phi}\\_b = & \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\left (\\overrightarrow{\\phi}\\_c + \\frac{\\overrightarrow{G}}{\\Delta} \\right)\\\\\\\\\n= &  (\\mathrm{I} - \\mathbf{vF}) \\cdot \\overrightarrow{\\phi}\\_c + \\overrightarrow{\\phi}\\_{ref}\\cdot vF + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\frac{\\overrightarrow{G}}{\\Delta}\n\\end{align}\n$$\n其中，$\\overrightarrow{\\phi}\\_{ref}=refValue$，$\\overrightarrow{G}=refGrad$，$\\mathbf{vF}=valueFraction$\n同样，为了方便分析，将上述公式的部分写成分量形式：\n$$\n\\begin{align}\n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} = & \\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix} - \\begin{bmatrix}\nvF\\_{xx}\\phi\\_{cx} + vF\\_{xy}\\phi\\_{cy} + vF\\_{xz}\\phi\\_{cz}\\\\\\\\\nvF\\_{yx}\\phi\\_{cx} + vF\\_{yy}\\phi\\_{cy} + vF\\_{yz}\\phi\\_{cz} \\\\\\\\\nvF\\_{zx}\\phi\\_{cx} + vF\\_{zy}\\phi\\_{cy} + vF\\_{zz}\\phi\\_{cz}\n\\end{bmatrix} + \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\frac{\\overrightarrow{G}}{\\Delta} \\\\\\\\\n= & \\begin{bmatrix}\n(1-vF\\_{xx})\\phi\\_{cx} \\\\\\\\\n(1-vF\\_{yy})\\phi\\_{cy} \\\\\\\\\n(1-vF\\_{zz})\\phi\\_{cz}\n\\end{bmatrix} - \\begin{bmatrix}\nvF\\_{xy}\\phi\\_{cy} + vF\\_{xz}\\phi\\_{cz}\\\\\\\\\nvF\\_{yx}\\phi\\_{cx} + vF\\_{yz}\\phi\\_{cz} \\\\\\\\\nvF\\_{zx}\\phi\\_{cx} + vF\\_{zy}\\phi\\_{cy} \n\\end{bmatrix} + \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\frac{\\overrightarrow{G}}{\\Delta}\n\\end{align}\n$$\n同样的，OpenFOAM 中四个系数的实现也与预期的不一样。主要还是 `snGradTransformDiag` 的定义与预期的不符:\n$$\nsnGradTransformDiag = \n\\begin{bmatrix}\n\\sqrt{|vF\\_{xx}|} \\\\\\\\\n\\sqrt{|vF\\_{yy}|} \\\\\\\\\n\\sqrt{|vF\\_{zz}|}\n\\end{bmatrix}\n$$\n\n结合代码，可以得到四个系数如下：\n$$\nvalueInternalCoeffs = \n\\begin{bmatrix}\n(1-\\sqrt{|vF\\_{xx}|}) \\\\\\\\\n(1-\\sqrt{|vF\\_{yy}|}) \\\\\\\\\n(1-\\sqrt{|vF\\_{zz}|})\n\\end{bmatrix}\n$$\n$$\nvalueBoundaryCoeffs = \n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} - valueInternalCoeffs \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n$$\ngradientInternalCoeffs= - \\Delta\n\\begin{bmatrix}\n\\sqrt{|vF\\_{xx}|} \\\\\\\\\n\\sqrt{|vF\\_{yy}|} \\\\\\\\\n\\sqrt{|vF\\_{zz}|}\n\\end{bmatrix}\n$$\n$$\ngradientBoundaryCoeffs = - \\mathbf{vF} \\cdot \\overrightarrow{\\phi}\\_c \\cdot \\Delta+ \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} \\cdot \\Delta + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\overrightarrow{G} - gradientInternalCoeffs \\cdot \\overrightarrow{\\phi}\\_c \n$$\n\n\n代码里的 `snGrad` 函数对应公式为：\n$$\n\\left [ \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot (\\overrightarrow{\\phi}\\_c + \\frac{\\overrightarrow{G}}{\\Delta}) - \\overrightarrow{\\phi}\\_c \\right ]\\cdot \\Delta\n$$\n\n不知道为什么 `snGradTransformDiag` 要按照这种方式来定义，可能是为了数值稳定性。不过，由于 `valueBoundaryCoeffs` 和 `gradientBoundaryCoeffs` 分别是在 `valueInternalCoeffs` 和 `gradientInternalCoeffs` 的基础之上定义的，所以总是能保证 `evaluate` 的结果与预期一致。\n\n可以将 `directionMixed` 的行为总结如下：\n![directionMixed 的行为](/image/boundaryConditions/boundary-conditions-in-openfoam-90.jpg)\n\n不过，如果 `valueFraction` 的值是任意指定的，而不是由 $\\overrightarrow{n}\\otimes\\overrightarrow{n}$ 构成的，那又另当别论了。\n\n**参考资料**：\n[Boundary Conditions in OpenFOAM](http://www.slideshare.net/fumiyanozaki96/boundary-conditions-in-openfoam)\n","source":"_posts/Boundary-conditions-in-OpenFOAM3.md","raw":"title: \"OpenFOAM 中的边界条件（三）\"\ndate: 2016-04-02 17:29:16\ncomments: true\ntags:\n- Boundary conditions\ncategories:\n- OpenFOAM\n---\n\nOpenFOAM 中有很多复杂的边界都是继承自上篇中提到的三个基础边界条件，这些边界条件的代码在上一篇的基础上就很容易看懂了。只不过，还有一些边界条件，不是继承自这三个基础边界条件的，其中有一些都直接或间接继承自另一个重要的边界条件： `transformFvPatchField`。本篇来看看这个 `transformFvPatchField` 以及几个继承自它的边界条件。\n\n<!--more-->\n\n##### 5. transform\n这是一个抽象基类，主要注意一下四个函数的定义：\n```\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return pTraits<Type>::one - snGradTransformDiag();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return\n        *this\n      - cmptMultiply\n        (\n            valueInternalCoeffs(this->patch().weights()),\n            this->patchInternalField()\n        );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return -this->patch().deltaCoeffs()*snGradTransformDiag();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > transformFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return\n        snGrad()\n      - cmptMultiply(gradientInternalCoeffs(), this->patchInternalField());\n}\n```\n由于 `snGrad` 和 `snGradTransformDiag` 都是纯虚函数，所以这四个函数的具体返回值需要在派生类中实现了 `snGrad` 和 `snGradTransformDiag` 之后才能确定。\n另外注意，当模板参数为 `scalar` 时， `gradientInternalCoeffs` 函数有特殊的定义：\n```\ntemplate<>\ntmp<scalarField > transformFvPatchField<scalar>::gradientInternalCoeffs() const\n{\n    return tmp<scalarField >(new scalarField(size(), 0.0));\n}\n```\n\n#####6. directionMixed\n这个类，跟前面的 `mixed` 有点类似，但是又继承自 `transform` ，所以，似乎是二者的结合。\n```\ntemplate<class Type>\nclass directionMixedFvPatchField\n:\n    public transformFvPatchField<Type>\n{\n    // Private data\n\n        //- Value field\n        Field<Type> refValue_;\n\n        //- Normal gradient field\n        Field<Type> refGrad_;\n\n        //- Fraction (0-1) of value used for boundary condition\n        symmTensorField valueFraction_;\n```\n与 `mixed` 相似之处是，这里也定义了 `refValue_` ， `refGrad_` 和 `valueFraction_` 三个参数，所不同的是，这里的 `valueFraction_` 是一个对称张量。\n\n接下来， `directionMixed` 定义了 `snGrad` 和 `snGradTransformDiag` 这两个函数\n+ `snGrad` 和 `snGradTransformDiag`\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::directionMixedFvPatchField<Type>::snGrad() const\n{\n    const Field<Type> pif(this->patchInternalField());\n\n    tmp<Field<Type> > normalValue = transform(valueFraction_, refValue_);\n\n    tmp<Field<Type> > gradValue = pif + refGrad_/this->patch().deltaCoeffs();\n\n    tmp<Field<Type> > transformGradValue =\n        transform(I - valueFraction_, gradValue);\n\n    return\n        (normalValue + transformGradValue - pif)*\n        this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::directionMixedFvPatchField<Type>::snGradTransformDiag() const\n{\n    vectorField diag(valueFraction_.size());\n\n    diag.replace\n    (\n        vector::X,\n        sqrt(mag(valueFraction_.component(symmTensor::XX)))\n    );\n    diag.replace\n    (\n        vector::Y,\n        sqrt(mag(valueFraction_.component(symmTensor::YY)))\n    );\n    diag.replace\n    (\n        vector::Z,\n        sqrt(mag(valueFraction_.component(symmTensor::ZZ)))\n    );\n\n    return transformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag));\n}\n```\n\n+ `evaluate` 函数\n```\ntemplate<class Type>\nvoid Foam::directionMixedFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    tmp<Field<Type> > normalValue = transform(valueFraction_, refValue_);\n\n    tmp<Field<Type> > gradValue =\n        this->patchInternalField() + refGrad_/this->patch().deltaCoeffs();\n\n    tmp<Field<Type> > transformGradValue =\n        transform(I - valueFraction_, gradValue);\n\n    Field<Type>::operator=(normalValue + transformGradValue);\n\n    transformFvPatchField<Type>::evaluate();\n}\n```\n\n##### 7. basicSymmetry\n这个类的结构与 `directionMixed` 类似，对 `snGrad` ， `snGradTransformDiag` 和 `evaluate` 等几个函数进行了重新定义。\n+  `snGrad` 和 `snGradTransformDiag`\n```\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::basicSymmetryFvPatchField<Type>::snGrad() const\n{\n    tmp<vectorField> nHat = this->patch().nf();\n\n    const Field<Type> iF(this->patchInternalField());\n\n    return\n        (transform(I - 2.0*sqr(nHat), iF) - iF)\n       *(this->patch().deltaCoeffs()/2.0);\n}\n\ntemplate<class Type>\nFoam::tmp<Foam::Field<Type> >\nFoam::basicSymmetryFvPatchField<Type>::snGradTransformDiag() const\n{\n    const vectorField nHat(this->patch().nf());\n\n    vectorField diag(nHat.size());\n\n    diag.replace(vector::X, mag(nHat.component(vector::X)));\n    diag.replace(vector::Y, mag(nHat.component(vector::Y)));\n    diag.replace(vector::Z, mag(nHat.component(vector::Z)));\n\n    return transformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag));\n}\n```\n\n+ evaluate\n```\ntemplate<class Type>\nvoid Foam::basicSymmetryFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    tmp<vectorField> nHat = this->patch().nf();\n\n    const Field<Type> iF(this->patchInternalField());\n\n    Field<Type>::operator=\n    (\n        (iF + transform(I - 2.0*sqr(nHat), iF))/2.0\n    );\n\n    transformFvPatchField<Type>::evaluate();\n}\n```\n另外，值得注意的是，当模板参数 `Type` 是 `scalar` 时， `snGrad` 和 `evaluate` 函数有其他的定义：\n```\ntemplate<>\nFoam::tmp<Foam::scalarField>\nFoam::basicSymmetryFvPatchField<Foam::scalar>::snGrad() const\n{\n    return tmp<scalarField >(new scalarField(size(), 0.0));\n}\n\ntemplate<>\nvoid Foam::basicSymmetryFvPatchField<Foam::scalar>::evaluate\n(\n    const Pstream::commsTypes\n)\n{\n    if (!updated())\n    {\n        updateCoeffs();\n    }\n\n    scalarField::operator=(patchInternalField());\n    transformFvPatchField<scalar>::evaluate();\n}\n```\n从这两个函数可推断，当 `Type = scalar` 时， `basicSymmetry` 其实就相当于 `zeroGradient`。 \n\n\n\n关于 `transform` 和 `transformFieldMask` 这两个函数，摸索了很久。前者涉及的源文件有 [`symmTransformField.C`](http://foam.sourceforge.net/docs/cpp/a07932_source.html) ，[ `transformFieldTemplates.C` ](http://foam.sourceforge.net/docs/cpp/a07942_source.html)；后者的定义在 [`symmTransformField.H`](http://foam.sourceforge.net/docs/cpp/a07933_source.html)，涉及到的 `pow` 函数的定义在[`FieldFunctions.C`](http://foam.sourceforge.net/docs/cpp/a07889_source.html)。此外，这两个函数还需要用到类似 `TFOR_ALL_F_OP_FUNC_F_F` 的宏，定义在[`fieldM.H`](http://foam.sourceforge.net/docs/cpp/a07893_source.html#l00153)，而这个宏里涉及到的类似 `List_ELEM` 这样的宏，则定义在 [`ListLoopM.H`](http://foam.sourceforge.net/docs/cpp/a07587_source.html)。\n\n看了这么多，仍然无法完全确定这两个函数的具体的行为。主要的障碍在于那个 `pow` 函数实在看不明白。最后只好来对这两个函数进行了一些测试，测试结果总结如下：\n```\ntransform(tensorField p1, vectorField p2)\n```\n返回的是另一个 `vectorField` ，其值等于 `p1` 与 `p2` 的内积（即点乘）。注意，一般使用过程中总能保证 `p1.size() == p2.size()`，但是如果 `p1.size() > p2.size()`， 则返回结果的 `size` 等于 `p2.size()` ，值则等于 `p1` 的前 `p2.size()` 部分与 `p2` 的内积。\n[Boundary Conditions in OpenFOAM](http://www.slideshare.net/fumiyanozaki96/boundary-conditions-in-openfoam) 这个 silde 也提到了29页也同样提到了 `transform` 函数的作用\n```\ninline scalar transform(constsymmTensor&, const scalar s)\n{\n    return s;\n}\n\ntemplate<class Cmpt>\ninline Vector<Cmpt> transform(const symmTensor& stt, const Vector<Cmpt>& v)\n{\n    return stt & v;\n}\n```\n\n而 `transformFieldMask`\n```\ntransformFieldMask<Type>(pow<vector, pTraits<Type>::rank>(diag))\n```\n目前测试的结果是，其返回值等于 `diag` 。\n\n\n有了上面对 `transform` 和 `transformFieldMask` 两个函数的测试结果，就可以来分析 `basicSymmetry` 和 `directionMixed` 两个边界条件的行为了。\n##### basicSymmetry\n对于标量，前面说过其等价于 `zeroGradient`，所以这里只分析矢量的情形。 \n从 `evaluate` 函数，可以得到如下公式\n$$\n\\begin{align}\n\\overrightarrow{\\phi}\\_b = & \\left [\\overrightarrow{\\phi}\\_c + (\\mathrm{I} - 2\\overrightarrow{n} \\otimes \\overrightarrow{n})\\cdot \\overrightarrow{\\phi}\\_c  \\right ] \\cdot \\frac{1}{2.0} \\\\\\\\\n= &  \\overrightarrow{\\phi}\\_c- \\left ( \\overrightarrow{\\phi}\\_c \\cdot  \\overrightarrow{n} \\right)\\cdot \\overrightarrow{n}\n\\end{align}\n$$\n这意味着，边界上的值等于其邻近网格中心的值的切向分量。\n为了方便分析四个系数，将上式写成分量的形式:\n$$\n\\begin{align}\n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} = & \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix} -\n\\begin{bmatrix}\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_x \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_y \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_z\n\\end{bmatrix} \\\\\\\\\n= & \\begin{bmatrix}\n(1-n\\_xn\\_x)\\phi\\_{cx} \\\\\\\\\n(1-n\\_yn\\_y)\\phi\\_{cy} \\\\\\\\\n(1-n\\_zn\\_z)\\phi\\_{cz}\n\\end{bmatrix} - \n\\begin{bmatrix}\n\\phi\\_{cy}n\\_yn\\_x + \\phi\\_{cz}n\\_zn\\_x \\\\\\\\\n\\phi\\_{cx}n\\_xn\\_y + \\phi\\_{cz}n\\_zn\\_y \\\\\\\\\n\\phi\\_{cx}n\\_xn\\_z + \\phi\\_{cy}n\\_yn\\_z \n\\end{bmatrix} \n\\end{align}\n$$\n照此公式，可以分析得到四个系数如下：\n$$\nvalueInternalCoeffs = \n\\begin{bmatrix}\n(1-n\\_xn\\_x) \\\\\\\\\n(1-n\\_yn\\_y) \\\\\\\\\n(1-n\\_zn\\_z)\n\\end{bmatrix}\n$$\n$$\nvalueBoundaryCoeffs = \n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} - valueInternalCoeffs \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n$$\ngradientInternalCoeffs= - \\Delta\n\\begin{bmatrix}\n(n\\_xn\\_x) \\\\\\\\\n(n\\_yn\\_y) \\\\\\\\\n(n\\_zn\\_z)\n\\end{bmatrix}\n$$\n$$\ngradientBoundaryCoeffs = - \\begin{bmatrix}\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_x \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_y \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_z\n\\end{bmatrix}  \\Delta - gradientInternalCoeffs  \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n\n** 但是，实际上 OpenFOAM 里不是这么实现的**！关键就在于这个 `snGradTransformDiag` 函数的定义与预期不符。\n根据我的测试， `snGradTransformDiag` 函数返回值应该是\n$$\nsnGradTransformDiag = \n\\begin{bmatrix}\n|n\\_x| \\\\\\\\\n|n\\_y| \\\\\\\\\n|n\\_z|\n\\end{bmatrix}\n$$\n即，张量 $\\overrightarrow{n}\\otimes\\overrightarrow{n}$ 的主对角线元素组成的矢量。\n\n而 `snGrad` 函数的返回值，根据代码可知\n$$\nsnGrad = - \\left ( \\overrightarrow{\\phi}\\_c \\cdot  \\overrightarrow{n} \\right)\\cdot \\overrightarrow{n}\\cdot \\Delta\n$$\n所以，OpenFOAM 中定义的四个系数为：\n$$\nvalueInternalCoeffs = \n\\begin{bmatrix}\n(1-|n\\_x|) \\\\\\\\\n(1-|n\\_y|) \\\\\\\\\n(1-|n\\_z|)\n\\end{bmatrix}\n$$\n$$\nvalueBoundaryCoeffs = \n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} - valueInternalCoeffs \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n$$\ngradientInternalCoeffs= - \\Delta\n\\begin{bmatrix}\n|n\\_x| \\\\\\\\\n|n\\_y| \\\\\\\\\n|n\\_z|\n\\end{bmatrix}\n$$\n$$\ngradientBoundaryCoeffs = - \\begin{bmatrix}\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_x \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_y \\\\\\\\\n(\\phi\\_{cx}n\\_x + \\phi\\_{cy}n\\_y + \\phi\\_{cz}n\\_z)n\\_z\n\\end{bmatrix}  \\Delta - gradientInternalCoeffs  \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n\n这里的 $\\Delta$ 代表代码中的 `deltaCoeffs` 。\n\n##### directionMixed \n`directionMixed` 与 `basicSymmetry` 是类似的，差别在于 `directionMixed` 所使用的对称张量是指定的，而不一定是 $\\overrightarrow{n}\\otimes\\overrightarrow{n}$。\n根据 `evaluate` 函数，可以得到如下公式：\n$$\n\\begin{align}\n\\overrightarrow{\\phi}\\_b = & \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\left (\\overrightarrow{\\phi}\\_c + \\frac{\\overrightarrow{G}}{\\Delta} \\right)\\\\\\\\\n= &  (\\mathrm{I} - \\mathbf{vF}) \\cdot \\overrightarrow{\\phi}\\_c + \\overrightarrow{\\phi}\\_{ref}\\cdot vF + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\frac{\\overrightarrow{G}}{\\Delta}\n\\end{align}\n$$\n其中，$\\overrightarrow{\\phi}\\_{ref}=refValue$，$\\overrightarrow{G}=refGrad$，$\\mathbf{vF}=valueFraction$\n同样，为了方便分析，将上述公式的部分写成分量形式：\n$$\n\\begin{align}\n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} = & \\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix} - \\begin{bmatrix}\nvF\\_{xx}\\phi\\_{cx} + vF\\_{xy}\\phi\\_{cy} + vF\\_{xz}\\phi\\_{cz}\\\\\\\\\nvF\\_{yx}\\phi\\_{cx} + vF\\_{yy}\\phi\\_{cy} + vF\\_{yz}\\phi\\_{cz} \\\\\\\\\nvF\\_{zx}\\phi\\_{cx} + vF\\_{zy}\\phi\\_{cy} + vF\\_{zz}\\phi\\_{cz}\n\\end{bmatrix} + \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\frac{\\overrightarrow{G}}{\\Delta} \\\\\\\\\n= & \\begin{bmatrix}\n(1-vF\\_{xx})\\phi\\_{cx} \\\\\\\\\n(1-vF\\_{yy})\\phi\\_{cy} \\\\\\\\\n(1-vF\\_{zz})\\phi\\_{cz}\n\\end{bmatrix} - \\begin{bmatrix}\nvF\\_{xy}\\phi\\_{cy} + vF\\_{xz}\\phi\\_{cz}\\\\\\\\\nvF\\_{yx}\\phi\\_{cx} + vF\\_{yz}\\phi\\_{cz} \\\\\\\\\nvF\\_{zx}\\phi\\_{cx} + vF\\_{zy}\\phi\\_{cy} \n\\end{bmatrix} + \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\frac{\\overrightarrow{G}}{\\Delta}\n\\end{align}\n$$\n同样的，OpenFOAM 中四个系数的实现也与预期的不一样。主要还是 `snGradTransformDiag` 的定义与预期的不符:\n$$\nsnGradTransformDiag = \n\\begin{bmatrix}\n\\sqrt{|vF\\_{xx}|} \\\\\\\\\n\\sqrt{|vF\\_{yy}|} \\\\\\\\\n\\sqrt{|vF\\_{zz}|}\n\\end{bmatrix}\n$$\n\n结合代码，可以得到四个系数如下：\n$$\nvalueInternalCoeffs = \n\\begin{bmatrix}\n(1-\\sqrt{|vF\\_{xx}|}) \\\\\\\\\n(1-\\sqrt{|vF\\_{yy}|}) \\\\\\\\\n(1-\\sqrt{|vF\\_{zz}|})\n\\end{bmatrix}\n$$\n$$\nvalueBoundaryCoeffs = \n\\begin{bmatrix}\n\\phi\\_{px} \\\\\\\\\n\\phi\\_{py} \\\\\\\\\n\\phi\\_{pz}\n\\end{bmatrix} - valueInternalCoeffs \n\\begin{bmatrix}\n\\phi\\_{cx} \\\\\\\\\n\\phi\\_{cy} \\\\\\\\\n\\phi\\_{cz}\n\\end{bmatrix}\n$$\n$$\ngradientInternalCoeffs= - \\Delta\n\\begin{bmatrix}\n\\sqrt{|vF\\_{xx}|} \\\\\\\\\n\\sqrt{|vF\\_{yy}|} \\\\\\\\\n\\sqrt{|vF\\_{zz}|}\n\\end{bmatrix}\n$$\n$$\ngradientBoundaryCoeffs = - \\mathbf{vF} \\cdot \\overrightarrow{\\phi}\\_c \\cdot \\Delta+ \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} \\cdot \\Delta + (\\mathrm{I} - \\mathbf{vF}) \\cdot \\overrightarrow{G} - gradientInternalCoeffs \\cdot \\overrightarrow{\\phi}\\_c \n$$\n\n\n代码里的 `snGrad` 函数对应公式为：\n$$\n\\left [ \\overrightarrow{\\phi}\\_{ref}\\cdot \\mathbf{vF} + (\\mathrm{I} - \\mathbf{vF}) \\cdot (\\overrightarrow{\\phi}\\_c + \\frac{\\overrightarrow{G}}{\\Delta}) - \\overrightarrow{\\phi}\\_c \\right ]\\cdot \\Delta\n$$\n\n不知道为什么 `snGradTransformDiag` 要按照这种方式来定义，可能是为了数值稳定性。不过，由于 `valueBoundaryCoeffs` 和 `gradientBoundaryCoeffs` 分别是在 `valueInternalCoeffs` 和 `gradientInternalCoeffs` 的基础之上定义的，所以总是能保证 `evaluate` 的结果与预期一致。\n\n可以将 `directionMixed` 的行为总结如下：\n![directionMixed 的行为](/image/boundaryConditions/boundary-conditions-in-openfoam-90.jpg)\n\n不过，如果 `valueFraction` 的值是任意指定的，而不是由 $\\overrightarrow{n}\\otimes\\overrightarrow{n}$ 构成的，那又另当别论了。\n\n**参考资料**：\n[Boundary Conditions in OpenFOAM](http://www.slideshare.net/fumiyanozaki96/boundary-conditions-in-openfoam)\n","slug":"Boundary-conditions-in-OpenFOAM3","published":1,"updated":"2016-04-03T07:44:44.840Z","layout":"post","photos":[],"link":"","_id":"cioiqegfz0054z8mb8wdqfvp2"},{"title":"OpenFOAM 中的边界条件（二）","date":"2016-04-02T08:40:43.000Z","comments":1,"_content":"\n本篇在上一篇的基础上来解读 OpenFOAM 中的基础边界条件。基础边界条件一般包括三类，一是Dirichlet 边界，二是 Neumann 边界，三是混合 Dirichlet 和 Neumann 的边界。\n\n<!--more-->\n\n##### 1. fixedValue\n这个是 OpenFOAM 中的 Dirichlet 边界条件。\n+ 构造函数\n```\ntemplate<class Type>\nfixedValueFvPatchField<Type>::fixedValueFvPatchField\n(\n    const fvPatch& p,\n    const DimensionedField<Type, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fvPatchField<Type>(p, iF, dict, true)\n{}\n```\n熟悉 OpenFOAM 的人都知道， `fixedValue` 这个边界条件需要用 `value` 关键字来指定边界的值。`value` 这个关键字是通过 `DimensionedField` 类来处理的。 `DimensionedField` 这个类将读取 `value` 关键字对应的场的值用来初始化边界上的值。\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return *this;\n}\n这里 \"*this\" 表示类本身，即当前边界上的值。这个值在上面的构造函数中进行了初始化，所以，可以理解为 valueBoundaryCoeffs 函数返回的正是关键字 \"value\" 所对应的值。\n\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return -pTraits<Type>::one*this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return this->patch().deltaCoeffs()*(*this);\n}\n```\n$$\n\\begin{align}\nvalueInternalCoeffs & = 0 \\\\\\\\\nvalueBoundaryCoeffs & = value \\\\\\\\\ngradientInternalCoeffs & = -delta \\\\\\\\\ngradientBoundaryCoeffs & = delta* value\n\\end{align}\n$$\n\n其中 $delta$ 为面心与面所属网格中心的距离的倒数。\n从上述系数，可以知道，fixedValue 边界条件对边界的值和梯度值的计算为如下：\n$$\n\\begin{align}\nx\\_p & = value \\\\\\\\\n\\nabla x\\_p & = - delta \\cdot x\\_C + delta \\cdot value = (value - x\\_C) \\cdot delta\n\\end{align}\n$$\n这与预期是一致的。\n\n\n##### 2. zeroGradient\n这个是 OpenFOAM 中的一种特殊的 Neumann 边界条件，即边界的梯度为零。\n+ evaluate 函数\n```\ntemplate<class Type>\nvoid zeroGradientFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    fvPatchField<Type>::operator==(this->patchInternalField());\n    fvPatchField<Type>::evaluate();\n}\n```\n注意，这里的 `operator==` 与 `operator=` 的作用是一样的，都是赋值运算，而不是比较。\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::one)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n```\n$$ \n\\begin{align}\nvalueInternalCoeffs & = 1 \\\\\\\\\nvalueBoundaryCoeffs & = 0 \\\\\\\\\ngradientInternalCoeffs & = 0 \\\\\\\\\ngradientBoundaryCoeffs & = 0\n\\end{align}\n$$\n\n从上述系数，可以知道，fixedValue 边界条件对边界的值和梯度值的计算为如下：\n\n$$\n\\begin{align}\nx\\_p & = x\\_C \\\\\\\\\n\\nabla x\\_p & = 0\n\\end{align}\n$$\n这与预期是一致的。\n\n##### 3. fixedGradient\n这个是 OpenFOAM 中的 Neumann 边界条件，可以指定边界上的梯度值。\n+ 构造函数\n```\ntemplate<class Type>\nfixedGradientFvPatchField<Type>::fixedGradientFvPatchField\n(\n    const fvPatch& p,\n    const DimensionedField<Type, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fvPatchField<Type>(p, iF, dict),\n    gradient_(\"gradient\", dict, p.size())\n{\n    evaluate();\n}\n```\n需要读取关键字 “gradient” 对应的值来初始化变量 `gradient_`。\n\n+ evaluate 函数\n```\ntemplate<class Type>\nvoid fixedGradientFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    Field<Type>::operator=\n    (\n        this->patchInternalField() + gradient_/this->patch().deltaCoeffs()\n    );\n\n    fvPatchField<Type>::evaluate();\n}\n\n```\n$$\nx\\_p = x\\_C + \\frac{gradient} {delta}\n$$\n其中 $delta$ 为面心与面所属网格中心的距离的倒数。\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >(new Field<Type>(this->size(), pTraits<Type>::one));\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return gradient()/this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::\ngradientInternalCoeffs() const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::\ngradientBoundaryCoeffs() const\n{\n    return gradient();\n}\n```\n$$ \n\\begin{align}\nvalueInternalCoeffs & = 1 \\\\\\\\\nvalueBoundaryCoeffs & = \\tfrac{gradient}{delta} \\\\\\\\\ngradientInternalCoeffs & = 0 \\\\\\\\\ngradientBoundaryCoeffs & = gradient\n\\end{align}\n$$\n\n##### 4. mixed\n这是 OpenFOAM 中的混合边界条件。\n+ 构造函数\n```\ntemplate<class Type>\nmixedFvPatchField<Type>::mixedFvPatchField\n(\n    const fvPatch& p,\n    const DimensionedField<Type, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fvPatchField<Type>(p, iF, dict),\n    refValue_(\"refValue\", dict, p.size()),\n    refGrad_(\"refGradient\", dict, p.size()),\n    valueFraction_(\"valueFraction\", dict, p.size())\n{\n    evaluate();\n}\n```\n需要读取三个参数。\n\n+ evaluate\n```\ntemplate<class Type>\nvoid mixedFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    Field<Type>::operator=\n    (\n        valueFraction_*refValue_\n      +\n        (1.0 - valueFraction_)*\n        (\n            this->patchInternalField()\n          + refGrad_/this->patch().deltaCoeffs()\n        )\n    );\n    fvPatchField<Type>::evaluate();\n}\n```\n$$\nx_p = valueFraction \\cdot refValue + (1-valueFraction) \\cdot (x_C + \\frac{refGrad}{delta})\n$$\n\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return Type(pTraits<Type>::one)*(1.0 - valueFraction_);\n}\n\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return\n         valueFraction_*refValue_\n       + (1.0 - valueFraction_)*refGrad_/this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return -Type(pTraits<Type>::one)*valueFraction_*this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return\n        valueFraction_*this->patch().deltaCoeffs()*refValue_\n      + (1.0 - valueFraction_)*refGrad_;\n}\n```\n$$ \n\\begin{align}\nvalueInternalCoeffs & = 1-valueFraction \\\\\\\\\nvalueBoundaryCoeffs & = valueFraction \\cdot refValue + (1-valueFraction) \\cdot \\tfrac{refGrad}{delta} \\\\\\\\\ngradientInternalCoeffs & = -valueFraction \\cdot delta \\\\\\\\\ngradientBoundaryCoeffs & = valueFraction \\cdot refValue \\cdot delta + (1-valueFraction) \\cdot refGrad \n\\end{align}\n$$\n\n附注：本篇中所有的下标 $p$ 都表示当前边界（present boundary patch），下标 $C$ 表示当前边界所属的网格的中心。\n","source":"_posts/Boundary-conditions-in-OpenFOAM2.md","raw":"title: \"OpenFOAM 中的边界条件（二）\"\ndate: 2016-04-02 16:40:43\ncomments: true\ntags:\n- Boundary conditions\ncategories:\n- OpenFOAM\n---\n\n本篇在上一篇的基础上来解读 OpenFOAM 中的基础边界条件。基础边界条件一般包括三类，一是Dirichlet 边界，二是 Neumann 边界，三是混合 Dirichlet 和 Neumann 的边界。\n\n<!--more-->\n\n##### 1. fixedValue\n这个是 OpenFOAM 中的 Dirichlet 边界条件。\n+ 构造函数\n```\ntemplate<class Type>\nfixedValueFvPatchField<Type>::fixedValueFvPatchField\n(\n    const fvPatch& p,\n    const DimensionedField<Type, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fvPatchField<Type>(p, iF, dict, true)\n{}\n```\n熟悉 OpenFOAM 的人都知道， `fixedValue` 这个边界条件需要用 `value` 关键字来指定边界的值。`value` 这个关键字是通过 `DimensionedField` 类来处理的。 `DimensionedField` 这个类将读取 `value` 关键字对应的场的值用来初始化边界上的值。\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return *this;\n}\n这里 \"*this\" 表示类本身，即当前边界上的值。这个值在上面的构造函数中进行了初始化，所以，可以理解为 valueBoundaryCoeffs 函数返回的正是关键字 \"value\" 所对应的值。\n\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return -pTraits<Type>::one*this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedValueFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return this->patch().deltaCoeffs()*(*this);\n}\n```\n$$\n\\begin{align}\nvalueInternalCoeffs & = 0 \\\\\\\\\nvalueBoundaryCoeffs & = value \\\\\\\\\ngradientInternalCoeffs & = -delta \\\\\\\\\ngradientBoundaryCoeffs & = delta* value\n\\end{align}\n$$\n\n其中 $delta$ 为面心与面所属网格中心的距离的倒数。\n从上述系数，可以知道，fixedValue 边界条件对边界的值和梯度值的计算为如下：\n$$\n\\begin{align}\nx\\_p & = value \\\\\\\\\n\\nabla x\\_p & = - delta \\cdot x\\_C + delta \\cdot value = (value - x\\_C) \\cdot delta\n\\end{align}\n$$\n这与预期是一致的。\n\n\n##### 2. zeroGradient\n这个是 OpenFOAM 中的一种特殊的 Neumann 边界条件，即边界的梯度为零。\n+ evaluate 函数\n```\ntemplate<class Type>\nvoid zeroGradientFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    fvPatchField<Type>::operator==(this->patchInternalField());\n    fvPatchField<Type>::evaluate();\n}\n```\n注意，这里的 `operator==` 与 `operator=` 的作用是一样的，都是赋值运算，而不是比较。\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::one)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > zeroGradientFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n```\n$$ \n\\begin{align}\nvalueInternalCoeffs & = 1 \\\\\\\\\nvalueBoundaryCoeffs & = 0 \\\\\\\\\ngradientInternalCoeffs & = 0 \\\\\\\\\ngradientBoundaryCoeffs & = 0\n\\end{align}\n$$\n\n从上述系数，可以知道，fixedValue 边界条件对边界的值和梯度值的计算为如下：\n\n$$\n\\begin{align}\nx\\_p & = x\\_C \\\\\\\\\n\\nabla x\\_p & = 0\n\\end{align}\n$$\n这与预期是一致的。\n\n##### 3. fixedGradient\n这个是 OpenFOAM 中的 Neumann 边界条件，可以指定边界上的梯度值。\n+ 构造函数\n```\ntemplate<class Type>\nfixedGradientFvPatchField<Type>::fixedGradientFvPatchField\n(\n    const fvPatch& p,\n    const DimensionedField<Type, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fvPatchField<Type>(p, iF, dict),\n    gradient_(\"gradient\", dict, p.size())\n{\n    evaluate();\n}\n```\n需要读取关键字 “gradient” 对应的值来初始化变量 `gradient_`。\n\n+ evaluate 函数\n```\ntemplate<class Type>\nvoid fixedGradientFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    Field<Type>::operator=\n    (\n        this->patchInternalField() + gradient_/this->patch().deltaCoeffs()\n    );\n\n    fvPatchField<Type>::evaluate();\n}\n\n```\n$$\nx\\_p = x\\_C + \\frac{gradient} {delta}\n$$\n其中 $delta$ 为面心与面所属网格中心的距离的倒数。\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return tmp<Field<Type> >(new Field<Type>(this->size(), pTraits<Type>::one));\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return gradient()/this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::\ngradientInternalCoeffs() const\n{\n    return tmp<Field<Type> >\n    (\n        new Field<Type>(this->size(), pTraits<Type>::zero)\n    );\n}\n\ntemplate<class Type>\ntmp<Field<Type> > fixedGradientFvPatchField<Type>::\ngradientBoundaryCoeffs() const\n{\n    return gradient();\n}\n```\n$$ \n\\begin{align}\nvalueInternalCoeffs & = 1 \\\\\\\\\nvalueBoundaryCoeffs & = \\tfrac{gradient}{delta} \\\\\\\\\ngradientInternalCoeffs & = 0 \\\\\\\\\ngradientBoundaryCoeffs & = gradient\n\\end{align}\n$$\n\n##### 4. mixed\n这是 OpenFOAM 中的混合边界条件。\n+ 构造函数\n```\ntemplate<class Type>\nmixedFvPatchField<Type>::mixedFvPatchField\n(\n    const fvPatch& p,\n    const DimensionedField<Type, volMesh>& iF,\n    const dictionary& dict\n)\n:\n    fvPatchField<Type>(p, iF, dict),\n    refValue_(\"refValue\", dict, p.size()),\n    refGrad_(\"refGradient\", dict, p.size()),\n    valueFraction_(\"valueFraction\", dict, p.size())\n{\n    evaluate();\n}\n```\n需要读取三个参数。\n\n+ evaluate\n```\ntemplate<class Type>\nvoid mixedFvPatchField<Type>::evaluate(const Pstream::commsTypes)\n{\n    if (!this->updated())\n    {\n        this->updateCoeffs();\n    }\n\n    Field<Type>::operator=\n    (\n        valueFraction_*refValue_\n      +\n        (1.0 - valueFraction_)*\n        (\n            this->patchInternalField()\n          + refGrad_/this->patch().deltaCoeffs()\n        )\n    );\n    fvPatchField<Type>::evaluate();\n}\n```\n$$\nx_p = valueFraction \\cdot refValue + (1-valueFraction) \\cdot (x_C + \\frac{refGrad}{delta})\n$$\n\n+ coefficients\n```\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::valueInternalCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return Type(pTraits<Type>::one)*(1.0 - valueFraction_);\n}\n\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::valueBoundaryCoeffs\n(\n    const tmp<scalarField>&\n) const\n{\n    return\n         valueFraction_*refValue_\n       + (1.0 - valueFraction_)*refGrad_/this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::gradientInternalCoeffs() const\n{\n    return -Type(pTraits<Type>::one)*valueFraction_*this->patch().deltaCoeffs();\n}\n\ntemplate<class Type>\ntmp<Field<Type> > mixedFvPatchField<Type>::gradientBoundaryCoeffs() const\n{\n    return\n        valueFraction_*this->patch().deltaCoeffs()*refValue_\n      + (1.0 - valueFraction_)*refGrad_;\n}\n```\n$$ \n\\begin{align}\nvalueInternalCoeffs & = 1-valueFraction \\\\\\\\\nvalueBoundaryCoeffs & = valueFraction \\cdot refValue + (1-valueFraction) \\cdot \\tfrac{refGrad}{delta} \\\\\\\\\ngradientInternalCoeffs & = -valueFraction \\cdot delta \\\\\\\\\ngradientBoundaryCoeffs & = valueFraction \\cdot refValue \\cdot delta + (1-valueFraction) \\cdot refGrad \n\\end{align}\n$$\n\n附注：本篇中所有的下标 $p$ 都表示当前边界（present boundary patch），下标 $C$ 表示当前边界所属的网格的中心。\n","slug":"Boundary-conditions-in-OpenFOAM2","published":1,"updated":"2016-05-30T16:01:39.866Z","_id":"cioiqegg20057z8mbb2h3fetp","layout":"post","photos":[],"link":""}],"PostAsset":[],"PostCategory":[{"post_id":"cioiqeg8v0000z8mboca1zeg7","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqeg9f0004z8mb8xz4nie7"},{"post_id":"cioiqega40006z8mbh4ha3suv","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqega70007z8mbldfjia89"},{"post_id":"cioiqegab000cz8mb775ky3ai","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegac000dz8mb555am2e5"},{"post_id":"cioiqegag000gz8mb5627c031","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegah000hz8mb28uixamn"},{"post_id":"cioiqegak000kz8mbdady6kfo","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegal000lz8mbpyao30kw"},{"post_id":"cioiqegb3000oz8mbvtahux4r","category_id":"cioiqegb5000pz8mbolorx8or","_id":"cioiqegb6000sz8mbw3qqku74"},{"post_id":"cioiqegbh000zz8mbjsizb3x4","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegbi0010z8mbt39ardzm"},{"post_id":"cioiqegbk0013z8mboot1zxyt","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegbm0014z8mbh061js8k"},{"post_id":"cioiqegbo0017z8mb7r2qzf5i","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegbp0018z8mbwfj3r7gi"},{"post_id":"cioiqegbs001bz8mbljlvnyh3","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegbu001cz8mbieych5ei"},{"post_id":"cioiqegbw001fz8mb79yhg58p","category_id":"cioiqegbx001gz8mb8w5905p5","_id":"cioiqegbz001jz8mbsaj4oc7z"},{"post_id":"cioiqegc3001nz8mbify9a45x","category_id":"cioiqegc7001oz8mbomkrb4uy","_id":"cioiqegca001rz8mbg22kjchm"},{"post_id":"cioiqegcd001tz8mbbms5fdry","category_id":"cioiqegcf001uz8mbzssncjjv","_id":"cioiqegcg001xz8mbdf9z65jr"},{"post_id":"cioiqegcq0025z8mbfx0zc315","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegct0026z8mbour8h8j2"},{"post_id":"cioiqegcv0029z8mb81a3mw5p","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegcx002az8mb1nzaulo6"},{"post_id":"cioiqegd4002dz8mbga0xvygg","category_id":"cioiqegd6002ez8mbzdcbpr46","_id":"cioiqegd7002hz8mb8fgbbn7h"},{"post_id":"cioiqegd9002iz8mbkn0g25pt","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdb002jz8mbxr4wmksn"},{"post_id":"cioiqegdf002nz8mbwwual2be","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdg002oz8mb18iisn5n"},{"post_id":"cioiqegdi002rz8mbgpk2lx0k","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdj002sz8mbn5yqg7rg"},{"post_id":"cioiqegdl002vz8mb32uz20uy","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdm002wz8mb7ld374ch"},{"post_id":"cioiqegdp002zz8mbqf4wbghq","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdq0030z8mbwunhvc1q"},{"post_id":"cioiqegdt0033z8mby34fv2qf","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdu0034z8mbvxjczq61"},{"post_id":"cioiqegdw0037z8mblc4pbpjd","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegdy0038z8mbkojqt44s"},{"post_id":"cioiqege0003cz8mb1va9knwn","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqege1003dz8mbj4m032ou"},{"post_id":"cioiqege3003gz8mboio07i4z","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegea003hz8mbx9597u3p"},{"post_id":"cioiqeged003kz8mb0v69imzd","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegef003lz8mbi00vx8ft"},{"post_id":"cioiqegem003qz8mbvvx8e26n","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegen003rz8mb0swagxu3"},{"post_id":"cioiqegeq003uz8mbexwzcz5j","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqeges003vz8mbhqmtfrnc"},{"post_id":"cioiqegex003yz8mb55c4967x","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegey003zz8mbu9dsn283"},{"post_id":"cioiqegf30043z8mbus88ehf2","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegf40044z8mbzj2tfsw4"},{"post_id":"cioiqegf60047z8mbwxcpxfzu","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegf70048z8mbvycvpyvl"},{"post_id":"cioiqegfe004fz8mb2gsy72pq","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegff004gz8mb9qccp4ub"},{"post_id":"cioiqegfh004jz8mbhpqtxe47","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegfj004kz8mba4vyu5fq"},{"post_id":"cioiqegfl004nz8mbkvoa8a40","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegfn004oz8mb5hlv2lf2"},{"post_id":"cioiqegfu004xz8mbakcrocw4","category_id":"cioiqegc7001oz8mbomkrb4uy","_id":"cioiqegfv004yz8mbot2ivghm"},{"post_id":"cioiqegfx0051z8mbgbon1ay4","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegfy0052z8mb1yc4ia0h"},{"post_id":"cioiqegfz0054z8mb8wdqfvp2","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"cioiqegg00055z8mbl5mxqtbh"},{"post_id":"cioiqegfq004sz8mbkjl2wrim","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"ciojmup0k000008mbckgsphku"},{"post_id":"cioiqegcj0020z8mb9wplgdgr","category_id":"cioiqegcm0021z8mb9ajjzmyc","_id":"ciomvbgcm00000omb38ez8pq4"},{"post_id":"cioiqegg20057z8mbb2h3fetp","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"ciou7dwmt0000qomb7ssr886z"},{"post_id":"cioiqegf9004bz8mbk5u820vc","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"ciowmwbby0000hsmbg7u0tol8"},{"post_id":"cioiqegba000vz8mb3he945cl","category_id":"cioiqeg970001z8mb9qlg9hfq","_id":"ciowne2nt0000g0mbmsneo2qh"}],"PostTag":[{"post_id":"cioiqeg8v0000z8mboca1zeg7","tag_id":"cioiqeg970002z8mbyw57yq2m","_id":"cioiqeg9f0003z8mbk8wifqs5"},{"post_id":"cioiqega40006z8mbh4ha3suv","tag_id":"cioiqega70008z8mbtffikl4h","_id":"cioiqega9000az8mbwraolq37"},{"post_id":"cioiqega40006z8mbh4ha3suv","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqega9000bz8mb6gjjuvwp"},{"post_id":"cioiqegab000cz8mb775ky3ai","tag_id":"cioiqega70008z8mbtffikl4h","_id":"cioiqegac000ez8mbe6ltehzz"},{"post_id":"cioiqegab000cz8mb775ky3ai","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegad000fz8mbxzrxkh4h"},{"post_id":"cioiqegag000gz8mb5627c031","tag_id":"cioiqega70008z8mbtffikl4h","_id":"cioiqegai000iz8mb3700m9op"},{"post_id":"cioiqegag000gz8mb5627c031","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegai000jz8mb9jb2jyph"},{"post_id":"cioiqegak000kz8mbdady6kfo","tag_id":"cioiqega70008z8mbtffikl4h","_id":"cioiqegam000mz8mbxzd9wiq1"},{"post_id":"cioiqegak000kz8mbdady6kfo","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegb0000nz8mbwwno70et"},{"post_id":"cioiqegb3000oz8mbvtahux4r","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegb7000tz8mbfj7tqq15"},{"post_id":"cioiqegb3000oz8mbvtahux4r","tag_id":"cioiqegb6000rz8mb4a1pzoxz","_id":"cioiqegb7000uz8mb2ba1c159"},{"post_id":"cioiqegba000vz8mb3he945cl","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegbd000xz8mbfxm7mnzo"},{"post_id":"cioiqegba000vz8mb3he945cl","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegbe000yz8mbybcy0tix"},{"post_id":"cioiqegbh000zz8mbjsizb3x4","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegbj0011z8mb3qh17prs"},{"post_id":"cioiqegbh000zz8mbjsizb3x4","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegbj0012z8mbdzunxmb4"},{"post_id":"cioiqegbk0013z8mboot1zxyt","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegbm0015z8mb921g4e8h"},{"post_id":"cioiqegbk0013z8mboot1zxyt","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegbm0016z8mbxtyysmgl"},{"post_id":"cioiqegbo0017z8mb7r2qzf5i","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegbq0019z8mb48mr68ib"},{"post_id":"cioiqegbo0017z8mb7r2qzf5i","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegbr001az8mb8omlp1pm"},{"post_id":"cioiqegbs001bz8mbljlvnyh3","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegbv001dz8mb0u7dwabp"},{"post_id":"cioiqegbs001bz8mbljlvnyh3","tag_id":"cioiqeg970002z8mbyw57yq2m","_id":"cioiqegbv001ez8mb5nstj2pg"},{"post_id":"cioiqegbw001fz8mb79yhg58p","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegbz001kz8mbcgwxhmzm"},{"post_id":"cioiqegbw001fz8mb79yhg58p","tag_id":"cioiqegby001hz8mbdbq9pkha","_id":"cioiqegc1001lz8mb4p42cxr4"},{"post_id":"cioiqegbw001fz8mb79yhg58p","tag_id":"cioiqegbz001iz8mb8krth3e8","_id":"cioiqegc1001mz8mbw8wq6bmb"},{"post_id":"cioiqegc3001nz8mbify9a45x","tag_id":"cioiqegc8001pz8mby7i0k9fk","_id":"cioiqegc9001qz8mboktxune2"},{"post_id":"cioiqegc3001nz8mbify9a45x","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegcb001sz8mbate76w71"},{"post_id":"cioiqegcd001tz8mbbms5fdry","tag_id":"cioiqegcf001vz8mbxvp8467k","_id":"cioiqegcg001yz8mbrmsza4wn"},{"post_id":"cioiqegcd001tz8mbbms5fdry","tag_id":"cioiqegcg001wz8mbxgpxzbwd","_id":"cioiqegch001zz8mb2kzg3wbf"},{"post_id":"cioiqegcj0020z8mb9wplgdgr","tag_id":"cioiqegcm0022z8mb1qi0cxs2","_id":"cioiqegcm0023z8mb5gjn1uyg"},{"post_id":"cioiqegcq0025z8mbfx0zc315","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegct0027z8mb4l17x2fy"},{"post_id":"cioiqegcq0025z8mbfx0zc315","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegct0028z8mbo97q02s0"},{"post_id":"cioiqegcv0029z8mb81a3mw5p","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegcz002bz8mb0e4zbf5e"},{"post_id":"cioiqegcv0029z8mb81a3mw5p","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegcz002cz8mb12kjsma8"},{"post_id":"cioiqegd4002dz8mbga0xvygg","tag_id":"cioiqegd6002fz8mb3dk84o35","_id":"cioiqegd7002gz8mbr03k6hmk"},{"post_id":"cioiqegd9002iz8mbkn0g25pt","tag_id":"cioiqegdc002kz8mb77ymbbhn","_id":"cioiqegdc002lz8mb7yyetl0f"},{"post_id":"cioiqegd9002iz8mbkn0g25pt","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegdd002mz8mb4bx3h5dz"},{"post_id":"cioiqegdf002nz8mbwwual2be","tag_id":"cioiqegdc002kz8mb77ymbbhn","_id":"cioiqegdg002pz8mbpzcm160s"},{"post_id":"cioiqegdf002nz8mbwwual2be","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegdg002qz8mbf8c28ix8"},{"post_id":"cioiqegdi002rz8mbgpk2lx0k","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegdj002tz8mbs3tq5btb"},{"post_id":"cioiqegdi002rz8mbgpk2lx0k","tag_id":"cioiqegcg001wz8mbxgpxzbwd","_id":"cioiqegdj002uz8mbj36sw35m"},{"post_id":"cioiqegdl002vz8mb32uz20uy","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegdm002xz8mb68z5ye2r"},{"post_id":"cioiqegdl002vz8mb32uz20uy","tag_id":"cioiqegcg001wz8mbxgpxzbwd","_id":"cioiqegdm002yz8mb3eh40t4t"},{"post_id":"cioiqegdp002zz8mbqf4wbghq","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegdr0031z8mb7bnzsu9f"},{"post_id":"cioiqegdp002zz8mbqf4wbghq","tag_id":"cioiqegcg001wz8mbxgpxzbwd","_id":"cioiqegdr0032z8mbv75c0v71"},{"post_id":"cioiqegdt0033z8mby34fv2qf","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqegdv0036z8mbkglfw68i"},{"post_id":"cioiqegdw0037z8mblc4pbpjd","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegdy003az8mblrgkc9sk"},{"post_id":"cioiqegdw0037z8mblc4pbpjd","tag_id":"cioiqegdy0039z8mb3pq1mrl8","_id":"cioiqegdz003bz8mb3d2z1yuy"},{"post_id":"cioiqege0003cz8mb1va9knwn","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqege1003ez8mbq3xairw1"},{"post_id":"cioiqege0003cz8mb1va9knwn","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqege2003fz8mbudmunu2t"},{"post_id":"cioiqege3003gz8mboio07i4z","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqegeb003iz8mboh197yd9"},{"post_id":"cioiqege3003gz8mboio07i4z","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegeb003jz8mbu5qibsjq"},{"post_id":"cioiqeged003kz8mb0v69imzd","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqegeh003nz8mbglppheau"},{"post_id":"cioiqeged003kz8mb0v69imzd","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegei003oz8mbha343h9z"},{"post_id":"cioiqeged003kz8mb0v69imzd","tag_id":"cioiqegeg003mz8mb9eno466t","_id":"cioiqegei003pz8mbhdw6sv8l"},{"post_id":"cioiqegem003qz8mbvvx8e26n","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqegeo003sz8mbdjjcz4fa"},{"post_id":"cioiqegem003qz8mbvvx8e26n","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegeo003tz8mbtmun9ejw"},{"post_id":"cioiqegeq003uz8mbexwzcz5j","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqeges003wz8mbhwfxfm8s"},{"post_id":"cioiqegeq003uz8mbexwzcz5j","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqeges003xz8mbdnyhf6ty"},{"post_id":"cioiqegex003yz8mb55c4967x","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegez0040z8mbtf9odm8w"},{"post_id":"cioiqegex003yz8mb55c4967x","tag_id":"cioiqegeg003mz8mb9eno466t","_id":"cioiqegez0041z8mb9rgyn3rz"},{"post_id":"cioiqegex003yz8mb55c4967x","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqegez0042z8mboomqeo81"},{"post_id":"cioiqegf30043z8mbus88ehf2","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegf50045z8mb78fbyc0l"},{"post_id":"cioiqegf30043z8mbus88ehf2","tag_id":"cioiqegeg003mz8mb9eno466t","_id":"cioiqegf50046z8mb3rdrtchh"},{"post_id":"cioiqegf60047z8mbwxcpxfzu","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegf80049z8mbd6q3d8ke"},{"post_id":"cioiqegf60047z8mbwxcpxfzu","tag_id":"cioiqegcg001wz8mbxgpxzbwd","_id":"cioiqegf8004az8mb76btdmjv"},{"post_id":"cioiqegf9004bz8mbk5u820vc","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegfc004dz8mbjttto5ll"},{"post_id":"cioiqegf9004bz8mbk5u820vc","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegfc004ez8mbvhsqztjp"},{"post_id":"cioiqegfe004fz8mb2gsy72pq","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegff004hz8mbnsbytpct"},{"post_id":"cioiqegfe004fz8mb2gsy72pq","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegfg004iz8mbl0xfhpf9"},{"post_id":"cioiqegfh004jz8mbhpqtxe47","tag_id":"cioiqegdu0035z8mbe5d3xxpq","_id":"cioiqegfk004lz8mb5u3tghqr"},{"post_id":"cioiqegfh004jz8mbhpqtxe47","tag_id":"cioiqega80009z8mbpjg2sn5n","_id":"cioiqegfk004mz8mbgai6hhae"},{"post_id":"cioiqegfl004nz8mbkvoa8a40","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegfo004qz8mbt9a0u855"},{"post_id":"cioiqegfl004nz8mbkvoa8a40","tag_id":"cioiqegfn004pz8mbxe52fyh2","_id":"cioiqegfo004rz8mbibs1mu1p"},{"post_id":"cioiqegfq004sz8mbkjl2wrim","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegft004vz8mbi2xamep8"},{"post_id":"cioiqegfq004sz8mbkjl2wrim","tag_id":"cioiqegfs004uz8mb00y6hh5a","_id":"cioiqegft004wz8mb793d7f22"},{"post_id":"cioiqegfu004xz8mbakcrocw4","tag_id":"cioiqegc8001pz8mby7i0k9fk","_id":"cioiqegfv004zz8mblilprv1m"},{"post_id":"cioiqegfu004xz8mbakcrocw4","tag_id":"cioiqegb5000qz8mbnp72u0jh","_id":"cioiqegfw0050z8mbe33gtevc"},{"post_id":"cioiqegfx0051z8mbgbon1ay4","tag_id":"cioiqeg970002z8mbyw57yq2m","_id":"cioiqegfy0053z8mblllkrvg5"},{"post_id":"cioiqegfz0054z8mb8wdqfvp2","tag_id":"cioiqeg970002z8mbyw57yq2m","_id":"cioiqegg10056z8mbo64hggis"},{"post_id":"cioiqegg20057z8mbb2h3fetp","tag_id":"cioiqeg970002z8mbyw57yq2m","_id":"cioiqegg30059z8mbss009it6"}],"Tag":[{"name":"Boundary conditions","_id":"cioiqeg970002z8mbyw57yq2m"},{"name":"wall functions","_id":"cioiqega70008z8mbtffikl4h"},{"name":"Code Explained","_id":"cioiqega80009z8mbpjg2sn5n"},{"name":"OpenFOAM","_id":"cioiqegb5000qz8mbnp72u0jh"},{"name":"vim","_id":"cioiqegb6000rz8mb4a1pzoxz"},{"name":"TIL","_id":"cioiqegby001hz8mbdbq9pkha"},{"name":"groovyBC","_id":"cioiqegbz001iz8mb8krth3e8"},{"name":"C++","_id":"cioiqegc8001pz8mby7i0k9fk"},{"name":"paraview","_id":"cioiqegcf001vz8mbxvp8467k"},{"name":"Postprocessing","_id":"cioiqegcg001wz8mbxgpxzbwd"},{"name":"LIGGGHTS","_id":"cioiqegcm0022z8mb1qi0cxs2"},{"name":"test","_id":"cioiqegd6002fz8mb3dk84o35"},{"name":"fvOptions","_id":"cioiqegdc002kz8mb77ymbbhn"},{"name":"turbulence model","_id":"cioiqegdu0035z8mbe5d3xxpq"},{"name":"Preprocessing","_id":"cioiqegdy0039z8mb3pq1mrl8"},{"name":"RTS","_id":"cioiqegeg003mz8mb9eno466t"},{"name":"Windows","_id":"cioiqegfn004pz8mbxe52fyh2"},{"name":"CentOS","_id":"cioiqegfs004uz8mb00y6hh5a"}]}}